Update code style to use rust fmt (#3131)

This commit is contained in:
Michael Mauderer 2021-11-02 14:05:43 +01:00 committed by GitHub
parent ab0f50a7a3
commit c822256e6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
432 changed files with 35600 additions and 29765 deletions

View File

@ -122,6 +122,8 @@ jobs:
run: npm install --save-dev --save-exact prettier
- name: Install Clippy
run: rustup component add clippy
- name: Install Clippy
run: rustup component add rustfmt
- name: Lint Markdown sources
run: npx prettier --check '*.md'
- name: Lint JavaScript sources

View File

@ -240,8 +240,8 @@ commands.test.rust = async function(argv) {
commands.lint = command(`Lint the codebase`)
commands.lint.rust = async function() {
// We run clippy-preview due to https://github.com/rust-lang/rust-clippy/issues/4612
await run_cargo('cargo',['clippy','--','-D','warnings'])
await run_cargo('cargo',['fmt','--','--check'])
}

View File

@ -128,6 +128,11 @@ let installClippy = {
run: 'rustup component add clippy',
}
let installFmt = {
name: "Install Clippy",
run: "rustup component add rustfmt"
}
// Install fixed version to avoid upgrading to a breaking version.
// Should be removed once this has a better solution as described here:
// https://github.com/enso-org/ide/issues/1772
@ -465,6 +470,7 @@ let workflow = {
installRust,
installPrettier,
installClippy,
installFmt,
lintMarkdown,
lintJavaScript,
lintRust,

View File

@ -1,43 +1,20 @@
---
layout: style-guide
title: Rust Style Guide
category: style-guide
tags: [style-guide,contributing]
layout: style-guide title: Rust Style Guide category: style-guide tags: [style-guide,contributing]
---
# Rust Style Guide
## Motivation - why not rely on a formatting tool for code style?
The code style is way more than just formatting. In many cases formatting can be
automated. According to rustfmt docs: "formatting code is a mostly mechanical
task which takes both time and mental effort. By using an automatic formatting
tool, a programmer is relieved of this task and can concentrate on more
important things.". While in many cases it is true, if the code author does not
take extra effort to make his code pretty by refactoring long lines to
variables, moving code to specific modules, or sections, the formatting tool
will result in code that is hard to read and hard to write. Thus, it is
important to take time to write code in such way that we can be proud of its
quality.
Because `rustfmt` does not support many of our requirements, we have created a
guide to describe how to format Rust code in this project. Please read it
carefully. We hope that in the future, some of the things described below will
be possible while using `rustfmt` (and we encourage you to contribute there!),
however, even if it happens, many parts of this guide will still be valid and
will need to be handled manually.
We are using `rustfmt` for the basic formatting of our rust code, which is also checked on CI. We
have some additional guidelines for imports, naming, sections within files and naming which are
documented here.
## Styling rules
### Code width
Each line in a source file should have max 80 chars of text (including
comments).
### Imports
Imports should be divided into 4 groups separated by blank lines. Items in the groups should be
sorted alphabetically.
### Imports
Imports should be divided into 4 groups separated by blank lines. Items in the
groups should be sorted alphabetically.
```rust
// Group 1: sub-module definitions.
// Group 2: prelude-like imports.
@ -46,6 +23,7 @@ groups should be sorted alphabetically.
```
For example:
```rust
pub mod display_object;
@ -60,17 +38,14 @@ use nalgebra::Matrix4;
use nalgebra::Vector3;
```
### Sections
Source files should be divided into sections. Section headers should be placed
before each new "concept" defined in a file. By "concept" we normally mean a
structure with related implementations. In case related implementations use some
helper structs with very small implementations, these helper structs may be
defined in the same section. Moreover, the code in each section should be
divided into sub-sections, grouping related definitions. At least one section
should be defined in a file (if there is at least one struct definition as
well). For example:
Source files should be divided into sections. Section headers should be placed before each new "
concept" defined in a file. By "concept" we normally mean a structure with related implementations.
In case related implementations use some helper structs with very small implementations, these
helper structs may be defined in the same section. Moreover, the code in each section should be
divided into sub-sections, grouping related definitions. At least one section should be defined in a
file (if there is at least one struct definition as well). For example:
```rust
// =================
@ -79,14 +54,13 @@ well). For example:
/// Defines the order in which particular axis coordinates are processed. Used
/// for example to define the rotation order in `DisplayObject`.
pub enum AxisOrder {XYZ,XZY,YXZ,YZX,ZXY,ZYX}
pub enum AxisOrder { XYZ, XZY, YXZ, YZX, ZXY, ZYX }
impl Default for AxisOrder {
fn default() -> Self {Self::XYZ}
fn default() -> Self { Self::XYZ }
}
// =================
// === Transform ===
// =================
@ -107,29 +81,28 @@ impl Default for TransformOrder {
}
// =============================
// === HierarchicalTransform ===
// =============================
pub struct HierarchicalTransform<OnChange> {
transform : Transform,
transform_matrix : Matrix4<f32>,
origin : Matrix4<f32>,
matrix : Matrix4<f32>,
pub dirty : dirty::SharedBool<OnChange>,
pub logger : Logger,
transform: Transform,
transform_matrix: Matrix4<f32>,
origin: Matrix4<f32>,
matrix: Matrix4<f32>,
pub dirty: dirty::SharedBool<OnChange>,
pub logger: Logger,
}
impl<OnChange> HierarchicalTransform<OnChange> {
pub fn new(logger:Logger, on_change:OnChange) -> Self {
let logger_dirty = logger.sub("dirty");
let transform = default();
pub fn new(logger: Logger, on_change: OnChange) -> Self {
let logger_dirty = logger.sub("dirty");
let transform = default();
let transform_matrix = Matrix4::identity();
let origin = Matrix4::identity();
let matrix = Matrix4::identity();
let dirty = dirty::SharedBool::new(logger_dirty,on_change);
Self {transform,transform_matrix,origin,matrix,dirty,logger}
let origin = Matrix4::identity();
let matrix = Matrix4::identity();
let dirty = dirty::SharedBool::new(logger_dirty, on_change);
Self { transform, transform_matrix, origin, matrix, dirty, logger }
}
}
@ -151,7 +124,7 @@ impl<OnChange> HierarchicalTransform<OnChange> {
// === Setters ===
impl<OnChange:Callback0> HierarchicalTransform<OnChange> {
impl<OnChange: Callback0> HierarchicalTransform<OnChange> {
pub fn position_mut(&mut self) -> &mut Vector3<f32> {
self.dirty.set();
&mut self.transform.position
@ -166,198 +139,63 @@ impl<OnChange:Callback0> HierarchicalTransform<OnChange> {
}
```
### Vertical spacing
We use the following amount of vertical spacing:
- 3 blank lines after imports.
- 3 blank lines before each section.
- 2 blank lines before each sub-section.
- 1 blank line after each section / sub-section.
- 1 blank line before functions / structures / impls.
- 1 blank line at the end of the file.
Please note that vertical spacing overlaps. For example, if there is a section
after imports, the total number of blank lines is 3, not 6.
### Multiline Expressions
Most (preferably all) expressions should be single line. Multiline expressions
are hard to read and introduce noise in the code. Often, it is also an indicator
of code that is not properly refactored. Try to refactor parts of multiline
expressions to well-named variables, and divide them to several single-line
expressions.
Most (preferably all) expressions should be single line. Multiline expressions are hard to read and
introduce noise in the code. Often, it is also an indicator of code that is not properly refactored.
Try to refactor parts of multiline expressions to well-named variables, and divide them to several
single-line expressions.
Example of poorly formatted code:
```rust
pub fn new() -> Self {
let shape_dirty = ShapeDirty::new(logger.sub("shape_dirty"),
on_dirty.clone());
on_dirty.clone());
let dirty_flag = MeshRegistryDirty::new(logger.sub("mesh_registry_dirty"),
on_dirty);
on_dirty);
Self { dirty_flag, dirty_flag }
}
```
Example of properly formatted code:
```rust
pub fn new() -> Self {
let sub_logger = logger.sub("shape_dirty");
let shape_dirty = ShapeDirty::new(sub_logger,on_dirty.clone());
let sub_logger = logger.sub("mesh_registry_dirty");
let dirty_flag = MeshRegistryDirty::new(sub_logger,on_dirty);
Self {shape_dirty,dirty_flag}
}
```
### Vertical alignment
The following elements should be aligned vertically in subsequent lines:
- assignment operators (`=`),
- type operators (`:`),
- match arrows (`=>`),
- similar parameters or types.
Examples:
```rust
impl Printer for GlobalVarStorage {
fn print(&self, builder:&mut Builder) {
match self {
Self::ConstStorage => build!(builder,"const"),
Self::UniformStorage => build!(builder,"uniform"),
Self::InStorage (qual) => build!(builder,"in" ,qual),
Self::OutStorage (qual) => build!(builder,"out",qual),
}
}
}
```
### Spaces
- Type operator is not spaced: `fn test(foo:String, bar:Int) { ... }`.
- Commas between complex expressions (including arg list) are spaced.
- Commas between simple elements are not spaced: `Result<Self,Error>`.
- Arguments to functions are not spaced: `build(builder,"out",qual)`.
- Operators are always spaced: `let foo = a + b * c;`.
### Function definitions
The following examples show proper function styles:
```rust
pub fn new<Dom:Str>(dom:Dom, logger:Logger) -> Result<Self,Error> {
...
}
```
```rust
pub fn new<Dom:Str>(dom:Dom, logger:Logger) -> Result<Self,Error> {
...
}
```
```rust
pub fn new<Dom:Str>
(dom:Dom, logger:Logger, on_dirty:OnDirty) -> Result<Self,Error> {
...
}
```
```rust
pub fn new<Dom:Str>
(dom:Dom, logger:Logger, on_dirty:OnDirty, on_remove:OnRemove)
-> Result<Self,Error> {
...
}
```
```rust
pub fn new<Dom:Str>
( dom : Dom
, logger : Logger
, on_dirty : OnDirty
, on_remove : OnRemove
, on_replace : OnReplace
) -> Result<Self,Error> {
...
}
```
Long `where` clauses are formatted this way:
```rust
pub fn new<D,L>(dom:D, logger:L) -> Result<Self,Error>
where D:AsRef<str>, L:IsLogger {
...
}
```
Or, in case they are really long, this way:
```rust
pub fn new<D,L>(dom:D, logger:L) -> Result<Self,Error>
where D:AsRef<str>
L:IsLogger
... {
...
}
```
### Impl definitions
Sometimes when browsing code it is hard to understand where the header of
an impl declaration is. Thus, the following style allows to find it easier.
The `where` block should be placed after a linebreak.
All of the following are correct:
```rust
// No constraints
impl<T> Printer for Option<T> {
...
}
```
```rust
// Some constraints
impl<T:Printer>
Printer for Option<T> {
...
}
```
```rust
// Constraints in where block
impl<T> Printer for Option<T>
where T: Printer {
...
let sub_logger = logger.sub("shape_dirty");
let shape_dirty = ShapeDirty::new(sub_logger, on_dirty.clone());
let sub_logger = logger.sub("mesh_registry_dirty");
let dirty_flag = MeshRegistryDirty::new(sub_logger, on_dirty);
Self { shape_dirty, dirty_flag }
}
```
### Getters and Setters
Getters do not have the `get_` prefix, while setters do. If a setter is
provided (method with the `set_` prefix), a `mut` accessor should be
provided as well. The correct way of defining getters and setters is
presented below:
Getters do not have the `get_` prefix, while setters do. If a setter is provided (method with
the `set_` prefix), a `mut` accessor should be provided as well. The correct way of defining getters
and setters is presented below:
```rust
fn field(&self) -> &Type {
&self.field
fn field(&self) -> &Type {
&self.field
}
fn field_mut(&mut self) -> &mut Type {
&mut self.field
fn field_mut(&mut self) -> &mut Type {
&mut self.field
}
fn set_field(&mut self, val:Type) {
fn set_field(&mut self, val: Type) {
*self.field_mut = val;
}
```
### Trait exporting
All names should be designed to be used in a qualified fashion. However, this makes one situation
All names should be designed to be used in a qualified fashion. However, this makes one situation
tricky. In order to use methods defined in a trait, it has to be in scope. Consider a trait
`display::Object`. We want to use it as function bound like `fn test<T:display::Object>(t:T) {...}`,
and we also want to use methods defined in this trait (so it has to be in scope). In such a case,
and we also want to use methods defined in this trait (so it has to be in scope). In such a case,
`Clippy` warns that `display::Object` is unnecessary qualification and could be replaced simply by
`Object`, which is not what we want. Thus, in order to export traits, please always rename them
using the following convention:

74
gui/rustfmt.toml Normal file
View File

@ -0,0 +1,74 @@
max_width = 100
hard_tabs = false
tab_spaces = 4
newline_style = "Unix"
indent_style = "Block"
use_small_heuristics = "Max"
fn_call_width = 100
attr_fn_like_width = 100
struct_lit_width = 100
struct_variant_width = 100
array_width = 100
chain_width = 100
single_line_if_else_max_width = 100
wrap_comments = true
format_code_in_doc_comments = true
comment_width = 100
normalize_comments = false
normalize_doc_attributes = false
license_template_path = ""
format_strings = false
format_macro_matchers = false
format_macro_bodies = true
empty_item_single_line = true
struct_lit_single_line = true
fn_single_line = false
where_single_line = true
imports_indent = "Block"
imports_layout = "Mixed"
imports_granularity = "Item"
group_imports = "Preserve"
reorder_imports = true
reorder_modules = true
reorder_impl_items = false
type_punctuation_density = "Wide"
space_before_colon = false
space_after_colon = true
spaces_around_ranges = false
binop_separator = "Front"
remove_nested_parens = true
combine_control_expr = true
overflow_delimited_expr = true
struct_field_align_threshold = 20
enum_discrim_align_threshold = 20
match_arm_blocks = false
match_arm_leading_pipes = "Never"
force_multiline_blocks = false
fn_args_layout = "Tall"
brace_style = "PreferSameLine"
control_brace_style = "AlwaysSameLine"
trailing_semicolon = true
trailing_comma = "Vertical"
match_block_trailing_comma = false
blank_lines_upper_bound = 3
blank_lines_lower_bound = 0
edition = "2018"
version = "One"
inline_attribute_width = 0
merge_derives = false
use_try_shorthand = false
use_field_init_shorthand = true
force_explicit_abi = true
condense_wildcard_suffixes = false
color = "Auto"
unstable_features = false
disable_all_formatting = false
skip_children = false
hide_parse_errors = false
error_on_line_overflow = false
error_on_unformatted = false
report_todo = "Never"
report_fixme = "Never"
ignore = []
emit_mode = "Files"
make_backup = false

View File

@ -1,36 +1,37 @@
#![feature(trait_alias)]
use std::path;
use std::io::ErrorKind;
use std::fmt::Display;
use std::io::ErrorKind;
use std::path;
/// Types that can yield a reference to std::path::Path.
pub trait PathRef = AsRef<path::Path>;
/// A structure describing a concrete release package on GitHub.
pub struct GithubRelease<T> {
pub project_url : T,
pub version : T,
pub filename : T,
pub project_url: T,
pub version: T,
pub filename: T,
}
impl<T:AsRef<str>+Display> GithubRelease<T> {
impl<T: AsRef<str> + Display> GithubRelease<T> {
/// Download the release package from GitHub if the target file was missing. Returns true if
/// the file was downloaded or false if it already existed.
///
/// The project_url should be a project's main page on GitHub.
pub fn download(&self, destination_dir:&path::Path) {
let url = format!("{}/releases/download/{}/{}",self.project_url,self.version,self.filename);
pub fn download(&self, destination_dir: &path::Path) {
let url =
format!("{}/releases/download/{}/{}", self.project_url, self.version, self.filename);
let destination_file = destination_dir.join(self.filename.as_ref());
Self::remove_old_file(&destination_file);
let mut resp = reqwest::blocking::get(&url).expect("Download failed.");
let mut out = std::fs::File::create(destination_file).expect("Failed to create file.");
let mut out = std::fs::File::create(destination_file).expect("Failed to create file.");
std::io::copy(&mut resp, &mut out).expect("Failed to copy file content.");
}
fn remove_old_file(file:&path::Path) {
let result = std::fs::remove_file(&file);
let error = result.err();
fn remove_old_file(file: &path::Path) {
let result = std::fs::remove_file(&file);
let error = result.err();
let fatal_error = error.filter(|err| err.kind() != ErrorKind::NotFound);
assert!(fatal_error.is_none());
}
@ -48,10 +49,10 @@ pub fn absolute_path(path: impl PathRef) -> std::io::Result<path::PathBuf> {
}
/// Get the environment variable or panic if not available.
pub fn env_var_or_panic(var_name:&str) -> String {
pub fn env_var_or_panic(var_name: &str) -> String {
match std::env::var(var_name) {
Ok(var) => var,
Err(e) => panic!("Failed to read environment variable {}: {}.", var_name, e),
Err(e) => panic!("Failed to read environment variable {}: {}.", var_name, e),
}
}

View File

@ -1,18 +1,20 @@
use std::{path, env};
use std::env;
use std::path;
/// A module with functions generating huge chunk of texts for text component benchmarks.
mod huge_text_generator {
use std::collections::hash_map::DefaultHasher;
use std::fs::File;
use std::hash::{Hash,Hasher};
use std::hash::Hash;
use std::hash::Hasher;
use std::io::Write;
use std::path::Path;
const MANY : usize = 100000;
const NOT_SO_MANY : usize = 100;
const MANY: usize = 100000;
const NOT_SO_MANY: usize = 100;
/// Create a file with many lines.
pub fn make_long_text_file(name:&Path) {
pub fn make_long_text_file(name: &Path) {
let mut file = File::create(name).unwrap();
for i in (1..MANY).rev() {
write_verse(&mut file, i);
@ -21,40 +23,42 @@ mod huge_text_generator {
}
/// Create a file with not so many long lines
pub fn make_wide_text_file(name:&Path) {
let mut file = File::create(name).unwrap();
let verses_in_line = MANY/NOT_SO_MANY;
pub fn make_wide_text_file(name: &Path) {
let mut file = File::create(name).unwrap();
let verses_in_line = MANY / NOT_SO_MANY;
for i in (1..MANY).rev() {
write_verse(&mut file, i);
if i % verses_in_line == 0 {
let line_index = i / verses_in_line;
let offset = hash_from(line_index) % 32;
let prefix = (0..offset).map(|_| '|').collect::<String>();
let offset = hash_from(line_index) % 32;
let prefix = (0..offset).map(|_| '|').collect::<String>();
writeln!(file).unwrap();
write!(file,"{}",prefix).unwrap();
write!(file, "{}", prefix).unwrap();
}
}
}
fn hash_from(i:usize) -> u64 {
fn hash_from(i: usize) -> u64 {
let mut hasher = DefaultHasher::new();
i.hash(&mut hasher);
hasher.finish()
}
fn write_verse(file:&mut File, i:usize) {
write!(file,
fn write_verse(file: &mut File, i: usize) {
write!(
file,
"{i} bottles of beer on the wall, {i} bottles of beer.\
Take one down and pass it around, {j} bottles of beer on the wall. ",
i = i,
j = i-1
).unwrap();
j = i - 1
)
.unwrap();
}
}
fn main() {
let out = env::var("OUT_DIR").unwrap();
let out = env::var("OUT_DIR").unwrap();
let out_dir = path::Path::new(&out);
huge_text_generator::make_long_text_file(out_dir.join("long.txt").as_path());
huge_text_generator::make_wide_text_file(out_dir.join("wide.txt").as_path());

View File

@ -2,8 +2,8 @@
use crate::prelude::*;
use ensogl_core::system::web;
use ensogl_core::application::Application;
use ensogl_core::system::web;
use ensogl_core::DEPRECATED_Animation;
use ensogl_text_msdf_sys::run_once_initialized;
use logger::TraceLogger as Logger;
@ -34,8 +34,7 @@ pub fn entry_point_animation() {
// ========================
fn init() {
let logger : Logger = Logger::new("AnimationTest");
let logger: Logger = Logger::new("AnimationTest");
let network = enso_frp::Network::new("test");
let animation = DEPRECATED_Animation::<f32>::new(&network);
animation.set_target_value(-259_830.0);

View File

@ -2,15 +2,15 @@
use ensogl_core::prelude::*;
use ensogl_core::data::color;
use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::system::web;
use wasm_bindgen::prelude::*;
use ensogl_core::display::object::ObjectOps;
use ensogl_core::display::world::*;
use ensogl_core::display::scene;
use ensogl_core::display::shape::*;
use ensogl_core::data::color;
use ensogl_core::display::style::theme;
use ensogl_core::display::world::*;
use ensogl_core::system::web;
use wasm_bindgen::prelude::*;
@ -58,30 +58,30 @@ pub fn entry_point_complex_shape_system() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();
let world = World::new(&web::get_html_element_by_id("root").unwrap());
let scene = world.scene();
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene,&camera);
let logger = Logger::new("ShapeView");
let world = World::new(&web::get_html_element_by_id("root").unwrap());
let scene = world.scene();
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene, &camera);
let logger = Logger::new("ShapeView");
let theme_manager = theme::Manager::from(&scene.style_sheet);
let theme1 = theme::Theme::new();
theme1.set("base_color", color::Rgba::new(0.0,0.0,1.0,1.0));
theme1.set("base_color", color::Rgba::new(0.0, 0.0, 1.0, 1.0));
theme1.set("animation.duration", 0.5);
theme1.set("graph.node.shadow.color", 5.0);
theme1.set("graph.node.shadow.size", 5.0);
theme1.set("mouse.pointer.color", color::Rgba::new(0.3,0.3,0.3,1.0));
theme1.set("mouse.pointer.color", color::Rgba::new(0.3, 0.3, 0.3, 1.0));
let theme2 = theme::Theme::new();
theme2.set("base_color", color::Rgba::new(0.0,1.0,0.0,1.0));
theme2.set("base_color", color::Rgba::new(0.0, 1.0, 0.0, 1.0));
theme2.set("animation.duration", 0.7);
theme2.set("graph.node.shadow.color", 5.0);
theme2.set("graph.node.shadow.size", 5.0);
theme2.set("mouse.pointer.color", color::Rgba::new(0.3,0.3,0.3,1.0));
theme2.set("mouse.pointer.color", color::Rgba::new(0.3, 0.3, 0.3, 1.0));
theme_manager.register("theme1",theme1);
theme_manager.register("theme2",theme2);
theme_manager.register("theme1", theme1);
theme_manager.register("theme2", theme2);
theme_manager.set_enabled(&["theme1".to_string()]);
@ -97,7 +97,8 @@ pub fn entry_point_complex_shape_system() {
mask.size.set(Vector2::new(300.0, 300.0));
mask.mod_position(|t| *t = Vector3::new(-50.0, 0.0, 0.0));
let scissor_box = scene::layer::ScissorBox::new_with_position_and_size(default(),Vector2(600,600));
let scissor_box =
scene::layer::ScissorBox::new_with_position_and_size(default(), Vector2(600, 600));
scene.layers.main.set_scissor_box(Some(&scissor_box));
let view2 = shape::View::new(&logger);
@ -111,51 +112,52 @@ pub fn entry_point_complex_shape_system() {
let scene = world.scene().clone_ref();
let mut frame = 0;
world.on_frame(move |_time| {
mask.set_position_x(((frame as f32)/30.0).sin()*100.0);
world
.on_frame(move |_time| {
mask.set_position_x(((frame as f32) / 30.0).sin() * 100.0);
let _keep_alive = &navigator;
let _keep_alive = &style_watch;
let _keep_alive = &theme_manager;
if frame == 50 {
// These comments are left for easy debugging in the future.
// DEBUG!("---------------");
// DEBUG!("{scene.layers.node_searcher:#?}");
// DEBUG!("{scene.layers.main:#?}");
// DEBUG!("{scene.layers.mask:#?}");
// DEBUG!("{scene.layers.node_searcher_mask:#?}");
// DEBUG!("{scene.layers.viz:#?}");
}
if frame == 100 {
DEBUG!("Adding previously hidden element.");
scene.add_child(&view2);
// These comments are left for easy debugging in the future.
// DEBUG!("---------------");
// DEBUG!("{scene.layers.node_searcher:#?}");
// DEBUG!("{scene.layers.main:#?}");
// DEBUG!("{scene.layers.mask:#?}");
// DEBUG!("{scene.layers.node_searcher_mask:#?}");
// DEBUG!("{scene.layers.viz:#?}");
}
if frame == 150 {
DEBUG!("Enabling masking.");
// These comments are left for easy debugging in the future.
// DEBUG!("---------------");
// DEBUG!("{scene.layers.node_searcher:#?}");
// DEBUG!("{scene.layers.main:#?}");
// DEBUG!("{scene.layers.mask:#?}");
// DEBUG!("{scene.layers.node_searcher_mask:#?}");
// DEBUG!("{scene.layers.viz:#?}");
scene.layers.node_searcher.add_exclusive(&view1);
scene.layers.node_searcher.add_exclusive(&view2);
scene.layers.node_searcher_mask.add_exclusive(&mask);
}
if frame == 200 {
DEBUG!("Changing the theme.");
theme_manager.set_enabled(&["theme2".to_string()]);
}
frame += 1;
}).forget();
let _keep_alive = &navigator;
let _keep_alive = &style_watch;
let _keep_alive = &theme_manager;
if frame == 50 {
// These comments are left for easy debugging in the future.
// DEBUG!("---------------");
// DEBUG!("{scene.layers.node_searcher:#?}");
// DEBUG!("{scene.layers.main:#?}");
// DEBUG!("{scene.layers.mask:#?}");
// DEBUG!("{scene.layers.node_searcher_mask:#?}");
// DEBUG!("{scene.layers.viz:#?}");
}
if frame == 100 {
DEBUG!("Adding previously hidden element.");
scene.add_child(&view2);
// These comments are left for easy debugging in the future.
// DEBUG!("---------------");
// DEBUG!("{scene.layers.node_searcher:#?}");
// DEBUG!("{scene.layers.main:#?}");
// DEBUG!("{scene.layers.mask:#?}");
// DEBUG!("{scene.layers.node_searcher_mask:#?}");
// DEBUG!("{scene.layers.viz:#?}");
}
if frame == 150 {
DEBUG!("Enabling masking.");
// These comments are left for easy debugging in the future.
// DEBUG!("---------------");
// DEBUG!("{scene.layers.node_searcher:#?}");
// DEBUG!("{scene.layers.main:#?}");
// DEBUG!("{scene.layers.mask:#?}");
// DEBUG!("{scene.layers.node_searcher_mask:#?}");
// DEBUG!("{scene.layers.viz:#?}");
scene.layers.node_searcher.add_exclusive(&view1);
scene.layers.node_searcher.add_exclusive(&view2);
scene.layers.node_searcher_mask.add_exclusive(&mask);
}
if frame == 200 {
DEBUG!("Changing the theme.");
theme_manager.set_enabled(&["theme2".to_string()]);
}
frame += 1;
})
.forget();
}

View File

@ -1,15 +1,15 @@
#![allow(missing_docs)]
use ensogl_core::traits::*;
use ensogl_core::prelude::*;
use ensogl_core::system::web;
use ensogl_core::system::web::NodeInserter;
use ensogl_core::display::symbol::DomSymbol;
use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::display::symbol::geometry::Sprite;
use ensogl_core::display::symbol::geometry::SpriteSystem;
use ensogl_core::display::symbol::DomSymbol;
use ensogl_core::display::world::*;
use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::system::web;
use ensogl_core::system::web::NodeInserter;
use web::StyleSetter;
use nalgebra::Vector2;
@ -21,49 +21,49 @@ use wasm_bindgen::prelude::*;
#[allow(clippy::many_single_char_names)]
pub fn entry_point_dom_symbols() {
web::forward_panic_hook_to_console();
let world = World::new(&web::get_html_element_by_id("root").unwrap());
let scene = world.scene();
let camera = scene.camera();
let screen = camera.screen();
let navigator = Navigator::new(scene,&camera);
let world = World::new(&web::get_html_element_by_id("root").unwrap());
let scene = world.scene();
let camera = scene.camera();
let screen = camera.screen();
let navigator = Navigator::new(scene, &camera);
let sprite_system = SpriteSystem::new(&world);
world.add_child(&sprite_system);
let dom_front_layer = &scene.dom.layers.front;
let dom_back_layer = &scene.dom.layers.back;
let dom_back_layer = &scene.dom.layers.back;
let mut sprites: Vec<Sprite> = default();
let mut sprites: Vec<Sprite> = default();
let mut css3d_objects: Vec<DomSymbol> = default();
let count = 10;
for i in 0 .. count {
let x = i as f32;
let width = screen.width * 1.5 / count as f32;
for i in 0..count {
let x = i as f32;
let width = screen.width * 1.5 / count as f32;
let height = screen.height;
let y = height / 2.0;
let y = height / 2.0;
if i % 2 == 0 {
let height = height * 0.75;
let size = Vector2::new(width, height);
let height = height * 0.75;
let size = Vector2::new(width, height);
let position = Vector3::new(width / 1.5 * x + width / 2.0, y, 0.0);
let sprite = sprite_system.new_instance();
let sprite = sprite_system.new_instance();
sprite.size.set(size);
sprite.mod_position(|t| *t = position);
sprites.push(sprite);
} else {
let div = web::create_div();
div.set_style_or_panic("width" , "100%");
div.set_style_or_panic("height" , "100%");
div.set_style_or_panic("width", "100%");
div.set_style_or_panic("height", "100%");
div.set_inner_html("top-left");
let size = Vector2::new(width, height);
let size = Vector2::new(width, height);
let position = Vector3::new(width / 1.5 * x + width / 2.0, y, 0.0);
let object = DomSymbol::new(&div);
let object = DomSymbol::new(&div);
dom_front_layer.manage(&object);
world.add_child(&object);
let r = ((x + 0.0) * 16.0) as u8;
let g = ((x + 2.0) * 32.0) as u8;
let b = ((x + 4.0) * 64.0) as u8;
let r = ((x + 0.0) * 16.0) as u8;
let g = ((x + 2.0) * 32.0) as u8;
let b = ((x + 4.0) * 64.0) as u8;
let color = iformat!("rgb({r},{g},{b})");
div.set_style_or_panic("background-color",color);
div.set_style_or_panic("background-color", color);
object.dom().append_or_panic(&div);
object.set_size(size);
@ -71,23 +71,25 @@ pub fn entry_point_dom_symbols() {
css3d_objects.push(object);
}
}
let layers = vec![dom_front_layer.clone_ref(),dom_back_layer.clone_ref()];
let layers = vec![dom_front_layer.clone_ref(), dom_back_layer.clone_ref()];
let mut iter_to_change = 0;
let mut i = 0;
world.keep_alive_forever();
world.on_frame(move |_| {
let _keep_alive = &navigator;
let _keep_alive = &sprites;
let _keep_alive = &sprite_system;
world
.on_frame(move |_| {
let _keep_alive = &navigator;
let _keep_alive = &sprites;
let _keep_alive = &sprite_system;
if iter_to_change == 0 {
iter_to_change = 50;
i = (i + 1) % 2;
for (j, object) in css3d_objects.iter_mut().enumerate() {
layers[(i + j) % 2].manage(object);
if iter_to_change == 0 {
iter_to_change = 50;
i = (i + 1) % 2;
for (j, object) in css3d_objects.iter_mut().enumerate() {
layers[(i + j) % 2].manage(object);
}
}
}
iter_to_change -= 1;
}).forget();
iter_to_change -= 1;
})
.forget();
}

View File

@ -3,24 +3,24 @@
use enso_prelude::*;
use ensogl_core::frp::web;
use ensogl_core::display::world::World;
use ensogl_core::frp::web;
use ensogl_web::drop;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use ensogl_web::drop;
fn download_file(file:drop::File) {
fn download_file(file: drop::File) {
spawn_local(async move {
INFO!("Received file: {file:?}");
loop {
match file.read_chunk().await {
Ok(Some(chunk)) => {
INFO!("Received chunk: {chunk:?}");
},
}
Ok(None) => {
INFO!("All chunks received successfully");
break
},
break;
}
Err(err) => {
ERROR!("Error in receiving chunk promise: {err:?}");
break;
@ -36,25 +36,25 @@ fn download_file(file:drop::File) {
pub fn entry_point_drop_manager() {
web::forward_panic_hook_to_console();
let world = World::new(&web::get_html_element_by_id("root").unwrap());
let world = World::new(&web::get_html_element_by_id("root").unwrap());
let drop_manager = drop::Manager::new(world.scene().dom.root.as_ref());
let network = enso_frp::Network::new("Debug Scene");
let network = enso_frp::Network::new("Debug Scene");
enso_frp::extend! { network
let file_received = drop_manager.files_received().clone_ref();
eval file_received ([](files) for file in files { download_file(file.clone_ref())});
}
let mut loader_hidden = false;
world.on_frame(move |_| {
if !loader_hidden {
web::get_element_by_id("loader").map(|t| {
t.parent_node().map(|p| {
p.remove_child(&t).unwrap()
})
}).ok();
loader_hidden = true;
}
}).forget();
world
.on_frame(move |_| {
if !loader_hidden {
web::get_element_by_id("loader")
.map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap()))
.ok();
loader_hidden = true;
}
})
.forget();
std::mem::forget(world);
std::mem::forget(network);

View File

@ -2,21 +2,21 @@
use crate::prelude::*;
use ensogl_core::animation::easing::*;
use ensogl_core::animation;
use ensogl_core::system::web::AttributeSetter;
use ensogl_core::animation::easing::*;
use ensogl_core::system::web;
use ensogl_core::system::web::create_element;
use ensogl_core::system::web::get_element_by_id;
use ensogl_core::system::web::AttributeSetter;
use ensogl_core::system::web::NodeInserter;
use ensogl_core::system::web::StyleSetter;
use ensogl_core::system::web;
use js_sys::Math;
use nalgebra::Vector2;
use std::ops::Add;
use std::ops::Mul;
use std::rc::Rc;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::CanvasRenderingContext2d;
use web_sys::HtmlCanvasElement;
use web_sys::HtmlElement;
@ -28,44 +28,44 @@ use web_sys::HtmlElement;
// ==================
/// Look and feel properties of sprite objects.
#[derive(Clone,Copy,Debug,PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(missing_docs)]
pub struct SpriteData {
pub position : Vector2<f32>,
pub size : f64
pub position: Vector2<f32>,
pub size: f64,
}
impl SpriteData {
/// Constructor.
pub fn new(position:Vector2<f32>, size:f64) -> Self {
Self {position,size}
pub fn new(position: Vector2<f32>, size: f64) -> Self {
Self { position, size }
}
/// Creates random SpriteData.
pub fn random() -> Self {
let x = ((Math::random() - 0.5) * 2.0) as f32;
let y = ((Math::random() - 0.5) * 2.0) as f32;
let position = Vector2::new(x,y);
let size = Math::random() * 100.0;
Self::new(position,size)
let position = Vector2::new(x, y);
let size = Math::random() * 100.0;
Self::new(position, size)
}
}
impl Mul<f32> for SpriteData {
type Output = Self;
fn mul(self, rhs:f32) -> Self {
fn mul(self, rhs: f32) -> Self {
let position = self.position * rhs;
let size = self.size * rhs as f64;
SpriteData {position,size}
let size = self.size * rhs as f64;
SpriteData { position, size }
}
}
impl Add<SpriteData> for SpriteData {
type Output = Self;
fn add(self, rhs:Self) -> Self {
fn add(self, rhs: Self) -> Self {
let position = self.position + rhs.position;
let size = self.size + rhs.size;
SpriteData {position,size}
let size = self.size + rhs.size;
SpriteData { position, size }
}
}
@ -76,32 +76,32 @@ impl Add<SpriteData> for SpriteData {
// ==============
/// A simplified Canvas object used in the EasingAnimator example.
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
pub struct Canvas {
canvas : HtmlCanvasElement,
context : CanvasRenderingContext2d
canvas: HtmlCanvasElement,
context: CanvasRenderingContext2d,
}
impl Canvas {
/// Constructor.
pub fn new(container_id:&str) -> Self {
pub fn new(container_id: &str) -> Self {
let canvas = web::create_canvas();
canvas.set_style_or_panic("border", "1px solid black");
canvas.set_width (256);
canvas.set_width(256);
canvas.set_height(256);
let context = canvas.get_context("2d").unwrap().unwrap();
let context : CanvasRenderingContext2d = context.dyn_into().unwrap();
let context: CanvasRenderingContext2d = context.dyn_into().unwrap();
let app : HtmlElement = get_element_by_id(container_id).unwrap().dyn_into().unwrap();
let app: HtmlElement = get_element_by_id(container_id).unwrap().dyn_into().unwrap();
app.append_or_panic(&canvas);
Self {canvas,context}
Self { canvas, context }
}
/// Clears the canvas.
pub fn clear(&self) {
self.context.clear_rect(0.0,0.0,self.width(),self.height())
self.context.clear_rect(0.0, 0.0, self.width(), self.height())
}
/// Gets Canvas' width.
@ -115,7 +115,7 @@ impl Canvas {
}
/// Draw sprite of the provided properties.
pub fn draw_sprite(&self, data:SpriteData, color:&str) {
pub fn draw_sprite(&self, data: SpriteData, color: &str) {
let size = (20.0 + data.size) / self.height();
let point = data.position;
self.context.save();
@ -123,15 +123,20 @@ impl Canvas {
self.context.scale(self.width() / 2.0, self.height() / 2.0).ok();
self.context.set_line_width(2.0 / self.height());
self.context.translate(1.0, 1.0).ok();
self.context.fill_rect(point.x as f64 - size / 2.0,point.y as f64 - size / 2.0,size,size);
self.context.fill_rect(
point.x as f64 - size / 2.0,
point.y as f64 - size / 2.0,
size,
size,
);
self.context.restore();
}
/// Draw a 2D graph of the provided easing function.
pub fn draw_graph<F:Fn(f32)->f32>(&self, f:F, color:&str, time_ms:f32) {
pub fn draw_graph<F: Fn(f32) -> f32>(&self, f: F, color: &str, time_ms: f32) {
let time_ms = time_ms as f64;
let width = self.width() - 1.0;
let height = self.height();
let width = self.width() - 1.0;
let height = self.height();
self.context.set_stroke_style(&color.into());
self.context.begin_path();
@ -148,11 +153,11 @@ impl Canvas {
self.context.stroke();
self.context.set_fill_style(&color.into());
let width = 8.0 / width;
let width = 8.0 / width;
let height = 16.0 / height;
let time_s = time_ms / 1000.0;
let x = time_s / 2.0;
let y = f(x as f32) as f64;
let x = time_s / 2.0;
let y = f(x as f32) as f64;
self.context.fill_rect(x - width / 2.0, y - height / 2.0, width, height);
self.context.restore();
}
@ -160,35 +165,43 @@ impl Canvas {
#[allow(clippy::type_complexity)]
struct Sampler {
color : &'static str,
time : f32,
left_canvas : Canvas,
right_canvas : Canvas,
easing_animator : Animator<SpriteData,Box<dyn Fn(f32)->f32>, Box<dyn Fn(SpriteData)>>,
properties : Rc<Cell<SpriteData>>,
easing_function : Box<dyn Fn(f32)->f32>,
color: &'static str,
time: f32,
left_canvas: Canvas,
right_canvas: Canvas,
easing_animator: Animator<SpriteData, Box<dyn Fn(f32) -> f32>, Box<dyn Fn(SpriteData)>>,
properties: Rc<Cell<SpriteData>>,
easing_function: Box<dyn Fn(f32) -> f32>,
}
impl Sampler {
#[allow(trivial_casts)]
fn new<F>(color:&'static str, left_canvas:&Canvas, right_canvas:&Canvas, f:F) -> Self
where F:CloneableFnEasing {
let left_canvas = left_canvas.clone();
let right_canvas = right_canvas.clone();
let properties = Rc::new(Cell::new(SpriteData::new(Vector2::new(0.0,0.0),1.0)));
let start = SpriteData::random();
let end = SpriteData::random();
let prop = properties.clone();
let easing_function = Box::new(f.clone()) as Box<dyn Fn(f32)->f32>;
let easing_function2 = Box::new(f) as Box<dyn Fn(f32)->f32>;
let animation_cb = Box::new(move |t| prop.set(t)) as Box<dyn Fn(SpriteData)>;
let easing_animator = Animator::new(start,end,easing_function2,animation_cb,());
let time = 0.0;
fn new<F>(color: &'static str, left_canvas: &Canvas, right_canvas: &Canvas, f: F) -> Self
where F: CloneableFnEasing {
let left_canvas = left_canvas.clone();
let right_canvas = right_canvas.clone();
let properties = Rc::new(Cell::new(SpriteData::new(Vector2::new(0.0, 0.0), 1.0)));
let start = SpriteData::random();
let end = SpriteData::random();
let prop = properties.clone();
let easing_function = Box::new(f.clone()) as Box<dyn Fn(f32) -> f32>;
let easing_function2 = Box::new(f) as Box<dyn Fn(f32) -> f32>;
let animation_cb = Box::new(move |t| prop.set(t)) as Box<dyn Fn(SpriteData)>;
let easing_animator = Animator::new(start, end, easing_function2, animation_cb, ());
let time = 0.0;
easing_animator.set_duration(2000.0);
Self {color,time,left_canvas,right_canvas,easing_animator,properties,easing_function}
Self {
color,
time,
left_canvas,
right_canvas,
easing_animator,
properties,
easing_function,
}
}
fn render(&mut self, time_diff:f32) {
fn render(&mut self, time_diff: f32) {
self.time += time_diff;
if self.time > 3000.0 {
self.time = 0.0;
@ -197,8 +210,8 @@ impl Sampler {
animator.set_target_value_no_restart(SpriteData::random());
animator.reset();
}
self.left_canvas.draw_graph(&self.easing_function,self.color,self.time);
self.right_canvas.draw_sprite(self.properties.get(),self.color);
self.left_canvas.draw_graph(&self.easing_function, self.color, self.time);
self.right_canvas.draw_sprite(self.properties.get(), self.color);
}
}
@ -209,41 +222,41 @@ impl Sampler {
// ===============
struct Example {
_animator : animation::Loop<Box<dyn FnMut(animation::TimeInfo)>>
_animator: animation::Loop<Box<dyn FnMut(animation::TimeInfo)>>,
}
impl Example {
#[allow(trivial_casts)]
pub fn new
( name : &str
, ease_in : impl CloneableFnEasing
, ease_out : impl CloneableFnEasing
, ease_in_out : impl CloneableFnEasing
pub fn new(
name: &str,
ease_in: impl CloneableFnEasing,
ease_out: impl CloneableFnEasing,
ease_in_out: impl CloneableFnEasing,
) -> Self {
let example = web::create_div();
example.set_attribute_or_panic("id", name);
example.set_style_or_panic("margin", "10px");
let container : HtmlElement = get_element_by_id("examples").unwrap().dyn_into().unwrap();
let header : HtmlElement = create_element("center").dyn_into().unwrap();
let container: HtmlElement = get_element_by_id("examples").unwrap().dyn_into().unwrap();
let header: HtmlElement = create_element("center").dyn_into().unwrap();
header.set_style_or_panic("background-color", "black");
header.set_style_or_panic("color", "white");
header.set_inner_html(name);
example.append_or_panic(&header);
container.append_or_panic(&example);
let left_canvas = Canvas::new(name);
let left_canvas = Canvas::new(name);
let right_canvas = Canvas::new(name);
let mut sampler1 = Sampler::new("green",&left_canvas,&right_canvas,ease_in);
let mut sampler2 = Sampler::new("blue" ,&left_canvas,&right_canvas,ease_out);
let mut sampler3 = Sampler::new("red" ,&left_canvas,&right_canvas,ease_in_out);
let mut sampler1 = Sampler::new("green", &left_canvas, &right_canvas, ease_in);
let mut sampler2 = Sampler::new("blue", &left_canvas, &right_canvas, ease_out);
let mut sampler3 = Sampler::new("red", &left_canvas, &right_canvas, ease_in_out);
let _animator = animation::Loop::new(Box::new(move |time_info:animation::TimeInfo| {
let _animator = animation::Loop::new(Box::new(move |time_info: animation::TimeInfo| {
left_canvas.clear();
right_canvas.clear();
sampler1.render(time_info.frame);
sampler2.render(time_info.frame);
sampler3.render(time_info.frame);
}) as Box<dyn FnMut(animation::TimeInfo)>);
Self {_animator}
Self { _animator }
}
}
@ -271,5 +284,5 @@ pub fn entry_point_easing_animator() {
container.set_style_or_panic("position", "absolute");
container.set_style_or_panic("top", "0px");
web::body().append_or_panic(&container);
examples![expo,bounce,circ,quad,cubic,quart,quint,sine,back,elastic];
examples![expo, bounce, circ, quad, cubic, quart, quint, sine, back, elastic];
}

View File

@ -5,8 +5,8 @@ use ensogl_core::prelude::*;
use ensogl_core::data::color;
use ensogl_core::display::world::*;
use ensogl_core::system::web;
use ensogl_text_msdf_sys::run_once_initialized;
use ensogl_text::typeface::*;
use ensogl_text_msdf_sys::run_once_initialized;
use wasm_bindgen::prelude::*;
@ -19,16 +19,16 @@ pub fn entry_point_glyph_system() {
run_once_initialized(|| init(&World::new(&web::get_html_element_by_id("root").unwrap())));
}
fn init(world:&World) {
let fonts = world.scene().extension::<font::Registry>();
let font = fonts.load("DejaVuSans");
let glyph_system = glyph::System::new(world.scene(),font);
let height = 32.0;
let color = color::Rgba::new(0.5,0.0,0.0,1.0);
let glyph = glyph_system.new_glyph();
fn init(world: &World) {
let fonts = world.scene().extension::<font::Registry>();
let font = fonts.load("DejaVuSans");
let glyph_system = glyph::System::new(world.scene(), font);
let height = 32.0;
let color = color::Rgba::new(0.5, 0.0, 0.0, 1.0);
let glyph = glyph_system.new_glyph();
glyph.set_char('Q');
glyph.set_color(color);
glyph.size.set(Vector2(height,height));
glyph.size.set(Vector2(height, height));
world.add_child(&glyph_system);
world.add_child(&glyph);

View File

@ -13,14 +13,14 @@
/// leaked when the `Leak` is dropped.
#[derive(Debug)]
pub struct Leak<T> {
value: Option<T>
value: Option<T>,
}
impl<T> Leak<T> {
/// Constructor. The passed value will be prevented from being dropped. This will cause memory
/// leaks.
pub fn new(value:T) -> Self {
Self{value:Some(value)}
pub fn new(value: T) -> Self {
Self { value: Some(value) }
}
/// Return a reference to the wrapped value.

View File

@ -20,18 +20,16 @@
#![warn(unsafe_code)]
#![warn(unused_import_braces)]
#![warn(unused_qualifications)]
#![recursion_limit = "1024"]
#![recursion_limit="1024"]
#[allow(clippy::option_map_unit_fn)]
mod leak;
pub mod animation;
pub mod complex_shape_system;
pub mod dom_symbols;
pub mod drop_manager;
pub mod easing_animator;
pub mod glyph_system;
#[allow(clippy::option_map_unit_fn)]
mod leak;
pub mod list_view;
pub mod mouse_events;
pub mod scroll_area;
@ -43,6 +41,6 @@ pub mod text_area;
/// Common types that should be visible across the whole crate.
pub mod prelude {
pub use ensogl_core::prelude::*;
pub use super::leak::*;
pub use ensogl_core::prelude::*;
}

View File

@ -2,15 +2,15 @@
use crate::prelude::*;
use ensogl_core::system::web;
use ensogl_core::application::Application;
use ensogl_core::display::object::ObjectOps;
use ensogl_text_msdf_sys::run_once_initialized;
use ensogl_core::system::web;
use ensogl_gui_components::list_view;
use ensogl_text::buffer::data::unit::Bytes;
use ensogl_text_msdf_sys::run_once_initialized;
use ensogl_theme as theme;
use logger::TraceLogger as Logger;
use wasm_bindgen::prelude::*;
use ensogl_text::buffer::data::unit::Bytes;
use ensogl_theme as theme;
@ -37,27 +37,29 @@ pub fn entry_point_list_view() {
// === Mock Entries ===
// ====================
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
struct MockEntries {
entries_count : usize,
entries_count: usize,
}
impl MockEntries {
fn new(entries_count:usize) -> Self {
Self {entries_count}
fn new(entries_count: usize) -> Self {
Self { entries_count }
}
}
impl list_view::entry::ModelProvider<list_view::entry::GlyphHighlightedLabel> for MockEntries {
fn entry_count(&self) -> usize { self.entries_count }
fn entry_count(&self) -> usize {
self.entries_count
}
fn get(&self, id:usize) -> Option<list_view::entry::GlyphHighlightedLabelModel> {
fn get(&self, id: usize) -> Option<list_view::entry::GlyphHighlightedLabelModel> {
if id >= self.entries_count {
None
} else {
let label = iformat!("Entry {id}");
let highlighted = if id == 10 { vec![(Bytes(1)..Bytes(3)).into()] } else { vec![] };
Some(list_view::entry::GlyphHighlightedLabelModel {label,highlighted})
Some(list_view::entry::GlyphHighlightedLabelModel { label, highlighted })
}
}
}
@ -68,20 +70,20 @@ impl list_view::entry::ModelProvider<list_view::entry::GlyphHighlightedLabel> fo
// === Init Application ===
// ========================
fn init(app:&Application) {
fn init(app: &Application) {
theme::builtin::dark::register(&app);
theme::builtin::light::register(&app);
theme::builtin::light::enable(&app);
let list_view = app.new_view::<list_view::ListView<list_view::entry::GlyphHighlightedLabel>>();
let provider = list_view::entry::AnyModelProvider::new(MockEntries::new(1000));
list_view.frp.resize(Vector2(100.0,160.0));
let provider = list_view::entry::AnyModelProvider::new(MockEntries::new(1000));
list_view.frp.resize(Vector2(100.0, 160.0));
list_view.frp.set_entries(provider);
app.display.add_child(&list_view);
// FIXME[WD]: This should not be needed after text gets proper depth-handling.
app.display.scene().layers.below_main.add_exclusive(&list_view);
let logger : Logger = Logger::new("SelectDebugScene");
let logger: Logger = Logger::new("SelectDebugScene");
let network = enso_frp::Network::new("test");
enso_frp::extend! {network
eval list_view.chosen_entry([logger](entry) {

View File

@ -3,7 +3,6 @@
use ensogl_core::prelude::*;
use wasm_bindgen::prelude::*;
use ensogl_core::system::web;
use ensogl_core::application;
use ensogl_core::application::Application;
use ensogl_core::data::color;
@ -12,6 +11,7 @@ use ensogl_core::display;
use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::display::object::ObjectOps;
use ensogl_core::display::shape::*;
use ensogl_core::system::web;
use enso_frp as frp;
use ensogl_text_msdf_sys::run_once_initialized;
@ -36,23 +36,23 @@ mod shape {
// === Model ===
// =============
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
struct Model {
app : Application,
logger : DefaultTraceLogger,
display_object : display::object::Instance,
shape : shape::View,
app: Application,
logger: DefaultTraceLogger,
display_object: display::object::Instance,
shape: shape::View,
}
impl Model {
fn new(app:&Application) -> Self {
let app = app.clone_ref();
let logger = DefaultTraceLogger::new("Button");
fn new(app: &Application) -> Self {
let app = app.clone_ref();
let logger = DefaultTraceLogger::new("Button");
let display_object = display::object::Instance::new(&logger);
let shape = shape::View::new(&logger);
let shape = shape::View::new(&logger);
shape.size.set(Vector2::new(100.0, 100.0));
display_object.add_child(&shape);
Self{app,logger,display_object,shape}
Self { app, logger, display_object, shape }
}
}
@ -75,18 +75,18 @@ ensogl_core::define_endpoints! { [TRACE_ALL]
// === View ===
// ============
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
struct View {
frp : Frp,
model : Model,
frp: Frp,
model: Model,
}
impl View {
/// Constructor.
pub fn new(app: &Application) -> Self {
let frp = Frp::new();
let model = Model::new(app);
let events = &model.shape.events;
let frp = Frp::new();
let model = Model::new(app);
let events = &model.shape.events;
let network = &events.network;
frp::extend! { network
// FIXME [mwu] Currently only `mouse_over` and `mouse_out` events are delivered.
@ -98,27 +98,39 @@ impl View {
trace model.shape.events.on_drop;
}
Self {frp,model }
Self { frp, model }
}
}
impl display::Object for View {
fn display_object(&self) -> &display::object::Instance { &self.model.display_object }
fn display_object(&self) -> &display::object::Instance {
&self.model.display_object
}
}
impl Deref for View {
type Target = Frp;
fn deref(&self) -> &Self::Target { &self.frp }
fn deref(&self) -> &Self::Target {
&self.frp
}
}
impl application::command::FrpNetworkProvider for View {
fn network(&self) -> &frp::Network { &self.frp.network }
fn network(&self) -> &frp::Network {
&self.frp.network
}
}
impl application::View for View {
fn label() -> &'static str { "Circul" }
fn new(app:&Application) -> Self { View::new(app) }
fn app(&self) -> &Application { &self.model.app }
fn label() -> &'static str {
"Circul"
}
fn new(app: &Application) -> Self {
View::new(app)
}
fn app(&self) -> &Application {
&self.model.app
}
}
@ -134,13 +146,13 @@ pub fn entry_point_mouse_events() {
run_once_initialized(|| {
let app = Application::new(&web::get_html_element_by_id("root").unwrap());
let shape:View = app.new_view();
let shape: View = app.new_view();
shape.model.shape.size.set(Vector2::new(300.0, 300.0));
app.display.add_child(&shape);
let scene = app.display.scene();
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene,&camera);
let scene = app.display.scene();
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene, &camera);
std::mem::forget(shape);
std::mem::forget(navigator);

View File

@ -6,14 +6,16 @@ use wasm_bindgen::prelude::*;
use ensogl_core::application::Application;
use ensogl_core::data::color;
use ensogl_core::display::object::ObjectOps;
use ensogl_core::display::shape::Circle;
use ensogl_core::display::shape::PixelDistance;
use ensogl_core::display::shape::Rect;
use ensogl_core::display::shape::ShapeOps;
use ensogl_core::display::shape::ShapeSystem;
use ensogl_core::display::Sprite;
use ensogl_core::system::web;
use ensogl_gui_components::scroll_area::ScrollArea;
use ensogl_text_msdf_sys::run_once_initialized;
use ensogl_theme as theme;
use ensogl_gui_components::scroll_area::ScrollArea;
use ensogl_core::display::shape::{Circle, Rect, ShapeSystem};
use ensogl_core::display::shape::PixelDistance;
use ensogl_core::display::shape::ShapeOps;
use ensogl_core::display::Sprite;
@ -39,24 +41,24 @@ pub fn entry_point_scroll_area() {
// === Init Application ===
// ========================
fn init(app:&Application) {
fn init(app: &Application) {
theme::builtin::dark::register(&app);
theme::builtin::light::register(&app);
theme::builtin::light::enable(&app);
let scene = app.display.scene();
scene.camera().set_position_xy(Vector2(100.0,-100.0));
scene.camera().set_position_xy(Vector2(100.0, -100.0));
// === Background ===
let background_color = color::Rgba::new(0.9,0.9,0.9,1.0);
let background_size = (200.px(), 200.px());
let background_shape = Rect(background_size).corners_radius(5.5.px()).fill(background_color);
let background_system = ShapeSystem::new(scene,background_shape);
let background_color = color::Rgba::new(0.9, 0.9, 0.9, 1.0);
let background_size = (200.px(), 200.px());
let background_shape = Rect(background_size).corners_radius(5.5.px()).fill(background_color);
let background_system = ShapeSystem::new(scene, background_shape);
let background: Sprite = background_system.new_instance();
scene.add_child(&background);
background.size.set(Vector2::new(200.0,200.0));
background.size.set(Vector2::new(200.0, 200.0));
background.set_position_x(100.0);
background.set_position_y(-100.0);
std::mem::forget(background);
@ -66,17 +68,17 @@ fn init(app:&Application) {
let scroll_area = ScrollArea::new(app);
app.display.add_child(&scroll_area);
scroll_area.resize(Vector2(200.0,200.0));
scroll_area.resize(Vector2(200.0, 200.0));
scroll_area.set_content_width(300.0);
scroll_area.set_content_height(1000.0);
// === Content ===
let sprite_system = ShapeSystem::new(scene,&Circle(50.px()));
let sprite_system = ShapeSystem::new(scene, &Circle(50.px()));
let sprite: Sprite = sprite_system.new_instance();
scroll_area.content.add_child(&sprite);
sprite.size.set(Vector2::new(100.0,100.0));
sprite.size.set(Vector2::new(100.0, 100.0));
sprite.set_position_x(100.0);
sprite.set_position_y(-100.0);
std::mem::forget(sprite);

View File

@ -2,14 +2,14 @@
use ensogl_core::prelude::*;
use ensogl_core::data::color;
use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::system::web;
use wasm_bindgen::prelude::*;
use ensogl_core::display::object::ObjectOps;
use ensogl_core::display::shape::ShapeSystem;
use ensogl_core::display::world::*;
use ensogl_core::display::shape::*;
use ensogl_core::data::color;
use ensogl_core::display::world::*;
use ensogl_core::system::web;
use wasm_bindgen::prelude::*;
@ -19,12 +19,12 @@ use ensogl_core::data::color;
/// The shape definition.
pub fn shape() -> AnyShape {
let circle1 = Circle(50.px());
let circle_bg = circle1.translate_x(-(50.0.px()));
let circle1 = Circle(50.px());
let circle_bg = circle1.translate_x(-(50.0.px()));
let circle_sub = circle1.translate_y(-(50.0.px()));
let rect = Rect((100.0.px(),100.0.px()));
let shape = circle_bg + rect - circle_sub;
let shape = shape.fill(color::Rgb::new(1.0,0.0,0.0));
let rect = Rect((100.0.px(), 100.0.px()));
let shape = circle_bg + rect - circle_sub;
let shape = shape.fill(color::Rgb::new(1.0, 0.0, 0.0));
shape.into()
}
@ -40,12 +40,12 @@ pub fn shape() -> AnyShape {
pub fn entry_point_shape_system() {
web::forward_panic_hook_to_console();
let world = World::new(&web::get_html_element_by_id("root").unwrap());
let scene = world.scene();
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene,&camera);
let sprite_system = ShapeSystem::new(&world,&shape());
let sprite = sprite_system.new_instance();
let world = World::new(&web::get_html_element_by_id("root").unwrap());
let scene = world.scene();
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene, &camera);
let sprite_system = ShapeSystem::new(&world, &shape());
let sprite = sprite_system.new_instance();
sprite.size.set(Vector2::new(300.0, 300.0));
sprite.mod_position(|t| *t = Vector3::new(50.0, 50.0, 0.0));
@ -53,8 +53,10 @@ pub fn entry_point_shape_system() {
world.add_child(&sprite_system);
world.keep_alive_forever();
world.on_frame(move |_time| {
let _keep_alive = &sprite;
let _keep_alive = &navigator;
}).forget();
world
.on_frame(move |_time| {
let _keep_alive = &sprite;
let _keep_alive = &navigator;
})
.forget();
}

View File

@ -7,8 +7,8 @@ use ensogl_core::application::Application;
use ensogl_core::data::color;
use ensogl_core::display::object::ObjectOps;
use ensogl_core::system::web;
use ensogl_gui_components::selector::Bounds;
use ensogl_gui_components::selector;
use ensogl_gui_components::selector::Bounds;
use ensogl_text_msdf_sys::run_once_initialized;
use ensogl_theme as theme;
@ -31,16 +31,16 @@ pub fn entry_point_slider() {
});
}
fn make_number_picker(app:&Application) -> Leak<selector::NumberPicker> {
fn make_number_picker(app: &Application) -> Leak<selector::NumberPicker> {
let slider = app.new_view::<selector::NumberPicker>();
slider.frp.resize(Vector2(200.0,50.0));
slider.frp.resize(Vector2(200.0, 50.0));
app.display.add_child(&slider);
Leak::new(slider)
}
fn make_range_picker(app:&Application) -> Leak<selector::NumberRangePicker> {
fn make_range_picker(app: &Application) -> Leak<selector::NumberRangePicker> {
let slider = app.new_view::<selector::NumberRangePicker>();
slider.frp.resize(Vector2(400.0,50.0));
slider.frp.resize(Vector2(400.0, 50.0));
app.display.add_child(&slider);
Leak::new(slider)
}
@ -51,7 +51,7 @@ fn make_range_picker(app:&Application) -> Leak<selector::NumberRangePicker> {
// === Init Application ===
// ========================
fn init(app:&Application) {
fn init(app: &Application) {
theme::builtin::dark::register(&app);
theme::builtin::light::register(&app);
theme::builtin::light::enable(&app);
@ -60,19 +60,19 @@ fn init(app:&Application) {
slider1.inner().frp.allow_click_selection(true);
let slider2 = make_number_picker(app);
slider2.inner().frp.resize(Vector2(400.0,50.0));
slider2.inner().frp.set_bounds.emit(Bounds::new(-100.0,100.0));
slider2.inner().frp.resize(Vector2(400.0, 50.0));
slider2.inner().frp.set_bounds.emit(Bounds::new(-100.0, 100.0));
slider2.inner().set_position_y(50.0);
slider2.inner().frp.use_overflow_bounds(Bounds::new(-150.0,200.0));
slider2.inner().frp.use_overflow_bounds(Bounds::new(-150.0, 200.0));
slider2.inner().frp.set_caption(Some("Value:".to_string()));
let slider3 = make_range_picker(app);
slider3.inner().set_position_y(-100.0);
slider3.inner().set_track_color(color::Rgba::new(0.0,0.80,0.80,1.0));
slider3.inner().set_track_color(color::Rgba::new(0.0, 0.80, 0.80, 1.0));
let slider4 = make_range_picker(app);
slider4.inner().set_position_y(-200.0);
slider4.inner().frp.use_overflow_bounds(Bounds::new(-2.0,3.0));
slider4.inner().frp.use_overflow_bounds(Bounds::new(-2.0, 3.0));
slider4.inner().frp.set_caption(Some("Caption".to_string()));
slider4.inner().set_track_color(color::Rgba::new(0.5,0.70,0.70,1.0));
slider4.inner().set_track_color(color::Rgba::new(0.5, 0.70, 0.70, 1.0));
}

View File

@ -6,8 +6,8 @@ use wasm_bindgen::prelude::*;
use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::display::symbol::geometry::SpriteSystem;
use ensogl_core::display::world::*;
use ensogl_core::system::web::forward_panic_hook_to_console;
use ensogl_core::system::web;
use ensogl_core::system::web::forward_panic_hook_to_console;
#[wasm_bindgen]
@ -15,30 +15,32 @@ use ensogl_core::system::web;
pub fn entry_point_sprite_system() {
forward_panic_hook_to_console();
let world = World::new(&web::get_html_element_by_id("root").unwrap());
let scene = world.scene();
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene,&camera);
let world = World::new(&web::get_html_element_by_id("root").unwrap());
let scene = world.scene();
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene, &camera);
let sprite_system = SpriteSystem::new(&world);
let sprite2 = sprite_system.new_instance();
let sprite1 = sprite_system.new_instance();
sprite1.size.set(Vector2::new(15.0,15.0));
sprite2.size.set(Vector2::new(15.0,15.0));
let sprite2 = sprite_system.new_instance();
let sprite1 = sprite_system.new_instance();
sprite1.size.set(Vector2::new(15.0, 15.0));
sprite2.size.set(Vector2::new(15.0, 15.0));
scene.add_child(&sprite_system);
world.keep_alive_forever();
let mut i = 0;
world.on_frame(move |_| {
i += 1;
let _keep_alive = &navigator;
let _keep_alive = &sprite1;
let _keep_alive = &sprite2;
let _keep_alive = &sprite_system;
if i <= 100 {
sprite1.mod_position(|p| p.x += 1.0);
sprite2.mod_position(|p| p.y += 1.0);
}
}).forget();
world
.on_frame(move |_| {
i += 1;
let _keep_alive = &navigator;
let _keep_alive = &sprite1;
let _keep_alive = &sprite2;
let _keep_alive = &sprite_system;
if i <= 100 {
sprite1.mod_position(|p| p.x += 1.0);
sprite2.mod_position(|p| p.y += 1.0);
}
})
.forget();
}

View File

@ -2,15 +2,15 @@
use ensogl_core::traits::*;
use ensogl_core::animation;
use ensogl_core::display::camera::Camera2d;
use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::display::symbol::geometry::Sprite;
use ensogl_core::display::symbol::geometry::SpriteSystem;
use ensogl_core::display::world::*;
use ensogl_core::prelude::*;
use ensogl_core::system::web::forward_panic_hook_to_console;
use ensogl_core::system::web;
use ensogl_core::animation;
use ensogl_core::system::web::forward_panic_hook_to_console;
use nalgebra::Vector2;
use nalgebra::Vector3;
use wasm_bindgen::prelude::*;
@ -21,65 +21,67 @@ use wasm_bindgen::prelude::*;
pub fn entry_point_sprite_system_benchmark() {
forward_panic_hook_to_console();
let world = World::new(&web::get_html_element_by_id("root").unwrap());
let scene = world.scene();
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene,&camera);
let world = World::new(&web::get_html_element_by_id("root").unwrap());
let scene = world.scene();
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene, &camera);
let sprite_system = SpriteSystem::new(&world);
let sprite1 = sprite_system.new_instance();
sprite1.size.set(Vector2::new(10.0,10.0));
sprite1.mod_position(|t| *t = Vector3::new(5.0,5.0,0.0));
let sprite1 = sprite_system.new_instance();
sprite1.size.set(Vector2::new(10.0, 10.0));
sprite1.mod_position(|t| *t = Vector3::new(5.0, 5.0, 0.0));
scene.add_child(&sprite_system);
let mut sprites: Vec<Sprite> = default();
let count = 100;
for _ in 0 .. count {
for _ in 0..count {
let sprite = sprite_system.new_instance();
sprite.size.set(Vector2::new(1.0,1.0));
sprite.size.set(Vector2::new(1.0, 1.0));
sprites.push(sprite);
}
world.keep_alive_forever();
let mut iter:i32 = 0;
let mut iter: i32 = 0;
let mut i = 0;
world.on_frame(move |time| {
i += 1;
if i <= 100 {
sprite1.mod_position(|p| p.x += 1.0);
}
let _keep_alive = &camera;
let _keep_alive = &iter;
let _keep_alive = &sprite1;
let _keep_alive = &sprites;
let _keep_alive = &sprite_system;
let _keep_alive = &navigator;
// FIXME: these logs crash gui after some time!
world
.on_frame(move |time| {
i += 1;
if i <= 100 {
sprite1.mod_position(|p| p.x += 1.0);
}
let _keep_alive = &camera;
let _keep_alive = &iter;
let _keep_alive = &sprite1;
let _keep_alive = &sprites;
let _keep_alive = &sprite_system;
let _keep_alive = &navigator;
// FIXME: these logs crash gui after some time!
// println!("sprite count: {:?}",sprites.len());
// println!("sprite_system is visible? {:?}",sprite_system.is_visible());
// println!("sprite[5] is visible? {:?}",sprites[5].is_visible());
// println!("sprite count: {:?}",sprites.len());
// println!("sprite_system is visible? {:?}",sprite_system.is_visible());
// println!("sprite[5] is visible? {:?}",sprites[5].is_visible());
on_frame(&camera,time,&mut iter,&sprite1,&mut sprites,&sprite_system)
}).forget();
on_frame(&camera, time, &mut iter, &sprite1, &mut sprites, &sprite_system)
})
.forget();
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::many_single_char_names)]
pub fn on_frame
( camera : &Camera2d
, time : animation::TimeInfo
, iter : &mut i32
, sprite1 : &Sprite
, sprites : &mut Vec<Sprite>
, sprite_system : &SpriteSystem
pub fn on_frame(
camera: &Camera2d,
time: animation::TimeInfo,
iter: &mut i32,
sprite1: &Sprite,
sprites: &mut Vec<Sprite>,
sprite_system: &SpriteSystem,
) {
*iter += 1;
let cycle_duration = 300;
let pause_duration = 100;
let cycle_duration = 300;
let pause_duration = 100;
let sprite_diff_per_cycle = 100;
let mut frozen = false;
@ -87,7 +89,7 @@ pub fn on_frame
if *iter < cycle_duration {
for _ in 0..sprite_diff_per_cycle {
let sprite = sprite_system.new_instance();
sprite.size.set(Vector2::new(1.0,1.0));
sprite.size.set(Vector2::new(1.0, 1.0));
sprites.push(sprite);
}
} else if *iter < pause_duration + cycle_duration {
@ -122,7 +124,11 @@ pub fn on_frame
y += (z * 1.25 + t * 2.00).cos() * 0.5;
z += (x * 1.25 + t * 3.25).cos() * 0.5;
let position = Vector3::new(x * 150.0 + half_width - 75.0, y * 150.0 + half_height - 75.0, z * 150.0);
let position = Vector3::new(
x * 150.0 + half_width - 75.0,
y * 150.0 + half_height - 75.0,
z * 150.0,
);
sprite.set_position(position);
}
}

View File

@ -2,12 +2,12 @@
use ensogl_core::prelude::*;
use ensogl_core::application::Application;
use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::system::web;
use ensogl_text::Area;
use ensogl_text_msdf_sys::run_once_initialized;
use wasm_bindgen::prelude::*;
use ensogl_core::application::Application;
use ensogl_text::Area;
use ensogl_core::display::navigation::navigator::Navigator;
/// Main example runner.
@ -23,21 +23,25 @@ pub fn entry_point_text_area() {
});
}
fn init(app:&Application) {
fn init(app: &Application) {
let area = app.new_view::<Area>();
area.set_position_x(-100.0);
area.set_content("Et Eärello Endorenna utúlien.\nSinome maruvan ar Hildinyar tenn' Ambar-metta");
area.set_content(
"Et Eärello Endorenna utúlien.\nSinome maruvan ar Hildinyar tenn' Ambar-metta",
);
area.focus();
area.hover();
area.set_cursor_at_end();
let scene = app.display.scene();
let navigator = Navigator::new(scene,&scene.camera());
let scene = app.display.scene();
let navigator = Navigator::new(scene, &scene.camera());
app.display.scene().add_child(&area);
let keep = Some(area);
app.display.on_frame(move |_frame| {
let _ = &keep;
}).forget();
app.display
.on_frame(move |_frame| {
let _ = &keep;
})
.forget();
std::mem::forget(navigator);
}

View File

@ -9,11 +9,11 @@
use crate::prelude::*;
use enso_frp as frp;
use ensogl_core::application::Application;
use ensogl_core::application::command::CommandApi;
use ensogl_core::application;
use ensogl_core::display::shape::*;
use ensogl_core::application::command::CommandApi;
use ensogl_core::application::Application;
use ensogl_core::display;
use ensogl_core::display::shape::*;
@ -26,7 +26,7 @@ use ensogl_core::display;
/// `Component`.
pub trait Model {
/// Constructor.
fn new(app:&Application) -> Self;
fn new(app: &Application) -> Self;
}
@ -38,9 +38,9 @@ pub trait Model {
/// Frp that can be used in a Component. The FRP requires an initializer that will be called during
/// the construction of the component. `Default` + `CommandApi` are usually implemented when using
/// the `ensogl_core::define_endpoints!` macro to create an FRP API.
pub trait Frp<Model> : Default + CommandApi {
pub trait Frp<Model>: Default + CommandApi {
/// Frp initializer.
fn init(&self, app:&Application, model:&Model, style:&StyleWatchFrp);
fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp);
}
@ -51,43 +51,48 @@ pub trait Frp<Model> : Default + CommandApi {
/// Base struct for UI components in EnsoGL. Contains the Data/Shape model and the FPR exposing its
/// behaviour.
#[derive(CloneRef,Debug,Derivative)]
#[derivative(Clone(bound=""))]
pub struct Component<Model,Frp> {
#[derive(CloneRef, Debug, Derivative)]
#[derivative(Clone(bound = ""))]
pub struct Component<Model, Frp> {
/// Public FRP api of the Component.
pub frp : Rc<Frp>,
model : Rc<Model>,
pub frp: Rc<Frp>,
model: Rc<Model>,
/// Reference to the application the Component belongs to. Generally required for implementing
/// `application::View` and initialising the `Mode`l and `Frp` and thus provided by the
/// `Component`.
pub app : Application,
pub app: Application,
}
impl<M:Model,F:Frp<M>> Component<M,F> {
impl<M: Model, F: Frp<M>> Component<M, F> {
/// Constructor.
pub fn new(app:&Application) -> Self {
let app = app.clone_ref();
pub fn new(app: &Application) -> Self {
let app = app.clone_ref();
let model = Rc::new(M::new(&app));
let frp = F::default();
let frp = F::default();
let style = StyleWatchFrp::new(&app.display.scene().style_sheet);
frp.init(&app,&model,&style);
let frp = Rc::new(frp);
Self{frp,model,app}
frp.init(&app, &model, &style);
let frp = Rc::new(frp);
Self { frp, model, app }
}
}
impl<M: display::Object,F> display::Object for Component<M,F> {
impl<M: display::Object, F> display::Object for Component<M, F> {
fn display_object(&self) -> &display::object::Instance {
self.model.display_object()
}
}
impl<M,F:Frp<M>> Deref for Component<M,F> {
impl<M, F: Frp<M>> Deref for Component<M, F> {
type Target = F;
fn deref(&self) -> &Self::Target { &self.frp }
fn deref(&self) -> &Self::Target {
&self.frp
}
}
impl<M,F:application::command::FrpNetworkProvider> application::command::FrpNetworkProvider
for Component<M,F> {
fn network(&self) -> &frp::Network { self.frp.network() }
impl<M, F: application::command::FrpNetworkProvider> application::command::FrpNetworkProvider
for Component<M, F>
{
fn network(&self) -> &frp::Network {
self.frp.network()
}
}

View File

@ -6,12 +6,12 @@ use crate::list_view::entry::ModelProvider;
use enso_frp as frp;
use enso_frp;
use ensogl_core::DEPRECATED_Animation;
use ensogl_core::application::Application;
use ensogl_core::data::color;
use ensogl_core::display::shape::*;
use ensogl_core::display::shape::primitive::StyleWatch;
use ensogl_core::display;
use ensogl_core::display::shape::primitive::StyleWatch;
use ensogl_core::display::shape::*;
use ensogl_core::DEPRECATED_Animation;
use ensogl_text as text;
use ensogl_theme as theme;
@ -21,9 +21,9 @@ use ensogl_theme as theme;
// =================
/// Invisible dummy color to catch hover events.
const HOVER_COLOR : color::Rgba = color::Rgba::new(1.0,0.0,0.0,0.000_001);
const HOVER_COLOR: color::Rgba = color::Rgba::new(1.0, 0.0, 0.0, 0.000_001);
/// The width of the visualisation selection menu.
const MENU_WIDTH : f32 = 180.0;
const MENU_WIDTH: f32 = 180.0;
@ -99,31 +99,31 @@ ensogl_core::define_endpoints! {
/// A type of Entry used in DropDownMenu's ListView.
pub type Entry = list_view::entry::Label;
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
struct Model {
display_object : display::object::Instance,
display_object: display::object::Instance,
icon : arrow::View,
icon_overlay : chooser_hover_area::View,
icon: arrow::View,
icon_overlay: chooser_hover_area::View,
label : text::Area,
selection_menu : list_view::ListView<Entry>,
label: text::Area,
selection_menu: list_view::ListView<Entry>,
// `SingleMaskedProvider` allows us to hide the selected element.
content : RefCell<Option<list_view::entry::SingleMaskedProvider<Entry>>>,
content: RefCell<Option<list_view::entry::SingleMaskedProvider<Entry>>>,
}
impl Model {
fn new(app:&Application) -> Self {
let logger = Logger::new("drop_down_menu");
fn new(app: &Application) -> Self {
let logger = Logger::new("drop_down_menu");
let display_object = display::object::Instance::new(&logger);
let icon = arrow::View::new(&logger);
let icon_overlay = chooser_hover_area::View::new(&logger);
let icon = arrow::View::new(&logger);
let icon_overlay = chooser_hover_area::View::new(&logger);
let selection_menu = list_view::ListView::new(app);
let label = app.new_view::<text::Area>();
let content = default();
let label = app.new_view::<text::Area>();
let content = default();
Self{display_object,icon,icon_overlay,label,selection_menu,content}.init()
Self { display_object, icon, icon_overlay, label, selection_menu, content }.init()
}
fn init(self) -> Self {
@ -135,7 +135,7 @@ impl Model {
self
}
fn set_label(&self, label:&str) {
fn set_label(&self, label: &str) {
self.label.set_cursor(&default());
self.label.select_all();
self.label.insert(label);
@ -150,8 +150,10 @@ impl Model {
self.selection_menu.unset_parent()
}
fn get_content_item
(&self, id:Option<list_view::entry::Id>) -> Option<<Entry as list_view::entry::Entry>::Model> {
fn get_content_item(
&self,
id: Option<list_view::entry::Id>,
) -> Option<<Entry as list_view::entry::Entry>::Model> {
self.content.borrow().as_ref()?.get(id?)
}
@ -163,7 +165,7 @@ impl Model {
/// Item list [A, B, C]
/// Unmasked index [0, 1, 2]
/// Masked indices [0, na, 1]
fn get_unmasked_index(&self, ix:Option<usize>) -> Option<usize> {
fn get_unmasked_index(&self, ix: Option<usize>) -> Option<usize> {
Some(self.content.borrow().as_ref()?.unmasked_index(ix?))
}
}
@ -182,32 +184,34 @@ impl display::Object for Model {
/// UI entity that shows a button that opens a list of visualisations that can be selected from.
#[allow(missing_docs)]
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
pub struct DropDownMenu {
model : Rc<Model>,
pub frp : Frp,
model: Rc<Model>,
pub frp: Frp,
}
impl Deref for DropDownMenu {
type Target = Frp;
fn deref(&self) -> &Self::Target { &self.frp }
fn deref(&self) -> &Self::Target {
&self.frp
}
}
impl DropDownMenu {
/// Constructor.
pub fn new(app:&Application) -> Self {
let frp = Frp::new();
pub fn new(app: &Application) -> Self {
let frp = Frp::new();
let model = Rc::new(Model::new(app));
Self {model,frp}.init(app)
Self { model, frp }.init(app)
}
fn init(self, app:&Application) -> Self {
fn init(self, app: &Application) -> Self {
let network = &self.frp.network;
let frp = &self.frp;
let model = &self.model;
let frp = &self.frp;
let model = &self.model;
let scene = app.display.scene();
let mouse = &scene.mouse.frp;
let scene = app.display.scene();
let mouse = &scene.mouse.frp;
frp::extend! { network
@ -354,7 +358,7 @@ impl DropDownMenu {
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for
// shape system (#795)
let styles = StyleWatch::new(&app.display.scene().style_sheet);
let styles = StyleWatch::new(&app.display.scene().style_sheet);
let text_color = styles.get_color(theme::widget::list_view::text);
model.label.set_default_color(text_color);

View File

@ -7,10 +7,10 @@ use crate::prelude::*;
use crate::file_browser::model::*;
use ensogl_core::display::shape::*;
use std::path::PathBuf;
use ensogl_core::display;
use ensogl_core::display::shape::*;
use ensogl_core::display::Scene;
use std::path::PathBuf;
// ===========
@ -47,33 +47,39 @@ ensogl_core::define_endpoints! {
/// A file browser component. It allows to browse the content of a folder and it's subfolders and
/// emits an event when an entry is chosen.
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
pub struct FileBrowser {
logger : Logger,
frp : Frp,
display_object : display::object::Instance,
logger: Logger,
frp: Frp,
display_object: display::object::Instance,
}
impl Deref for FileBrowser {
type Target = Frp;
fn deref(&self) -> &Self::Target { &self.frp }
fn deref(&self) -> &Self::Target {
&self.frp
}
}
impl FileBrowser {
/// Constructore
pub fn new() -> Self {
let logger = Logger::new("FileBrowser");
let frp = Frp::new();
let logger = Logger::new("FileBrowser");
let frp = Frp::new();
let display_object = display::object::Instance::new(&logger);
Self {logger,frp,display_object}
Self { logger, frp, display_object }
}
}
impl Default for FileBrowser {
fn default() -> Self { Self::new() }
fn default() -> Self {
Self::new()
}
}
impl display::Object for FileBrowser {
fn display_object(&self) -> &display::object::Instance<Scene> {&self.display_object }
fn display_object(&self) -> &display::object::Instance<Scene> {
&self.display_object
}
}

View File

@ -4,8 +4,8 @@
use crate::prelude::*;
use enso_frp as frp;
use std::path::PathBuf;
use std::cmp::Ordering;
use std::path::PathBuf;
// =============
@ -16,7 +16,7 @@ use std::cmp::Ordering;
/// The type of a folder. This is used to distinguish standard folders from the different kinds of
/// content roots.
#[derive(Debug,Copy,Clone,Eq,Ord,PartialEq,PartialOrd)]
#[derive(Debug, Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub enum FolderType {
/// A normal sufolder in the file system.
Standard,
@ -34,49 +34,54 @@ pub enum FolderType {
/// The type of a file system entry. Distinguishes files from the different kind of folders. The
/// `EntryType` of a folder also caries the folder's content.
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
pub enum EntryType {
/// A file.
File,
/// A folder. This can also mean a content root.
Folder {
/// The folder type.
type_ : FolderType,
type_: FolderType,
/// The folder's content.
content : AnyFolderContent,
content: AnyFolderContent,
},
}
impl Ord for EntryType {
fn cmp(&self, other:&Self) -> Ordering {
match (self,other) {
(Self::File,Self::File) => Ordering::Equal,
(Self::File,Self::Folder {..}) => Ordering::Greater,
(Self::Folder {..},Self::File) => Ordering::Less,
(Self::Folder {type_:type1,..},Self::Folder {type_:type2,..}) => type1.cmp(type2),
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Self::File, Self::File) => Ordering::Equal,
(Self::File, Self::Folder { .. }) => Ordering::Greater,
(Self::Folder { .. }, Self::File) => Ordering::Less,
(Self::Folder { type_: type1, .. }, Self::Folder { type_: type2, .. }) =>
type1.cmp(type2),
}
}
}
impl PartialOrd for EntryType {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for EntryType {
fn eq(&self, other: &Self) -> bool { self.cmp(other) == Ordering::Equal }
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for EntryType {}
/// A file system entry. Either a file or a folder.
#[derive(Debug,Clone,Eq,Ord,PartialEq,PartialOrd)]
#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub struct Entry {
/// The entry type.
pub type_ : EntryType,
pub type_: EntryType,
/// The entrie's name.
pub name : String,
pub name: String,
/// The entrie's global path in the file system.
pub path : PathBuf,
pub path: PathBuf,
}
@ -87,13 +92,16 @@ pub struct Entry {
pub trait FolderContent: Debug {
/// Request the list of entries inside the folder. When the list is ready, it is emitted at
/// `entries_loaded`. If an error occurs then the error message is emitted at `error_occurred`.
fn request_entries
(&self, entries_loaded:frp::Any<Rc<Vec<Entry>>>, error_occurred:frp::Any<ImString>);
fn request_entries(
&self,
entries_loaded: frp::Any<Rc<Vec<Entry>>>,
error_occurred: frp::Any<ImString>,
);
}
/// A wrapper around `Rc<dyn FolderContent>`. Necessary to implement the `Default` trait on this
/// type, which we need to pass it through FRP networks.
#[derive(Debug,Clone)]
#[derive(Debug, Clone)]
pub struct AnyFolderContent(Rc<dyn FolderContent>);
impl Deref for AnyFolderContent {
@ -104,7 +112,7 @@ impl Deref for AnyFolderContent {
}
}
impl<D:'static + FolderContent> From<D> for AnyFolderContent {
impl<D: 'static + FolderContent> From<D> for AnyFolderContent {
fn from(dir: D) -> Self {
AnyFolderContent(Rc::new(dir))
}
@ -114,12 +122,15 @@ impl<D:'static + FolderContent> From<D> for AnyFolderContent {
// === EmptyFolder ===
/// `FolderContent` that immediately provides an empty content list on request.
#[derive(Debug,Copy,Clone)]
#[derive(Debug, Copy, Clone)]
pub struct EmptyFolderContent;
impl FolderContent for EmptyFolderContent {
fn request_entries
(&self, entries_loaded:frp::Any<Rc<Vec<Entry>>>, _error_occured:frp::Any<ImString>) {
fn request_entries(
&self,
entries_loaded: frp::Any<Rc<Vec<Entry>>>,
_error_occured: frp::Any<ImString>,
) {
entries_loaded.emit(Rc::new(vec![]));
}
}

View File

@ -7,9 +7,9 @@ use enso_frp as frp;
use enso_frp;
use ensogl_core::application::Application;
use ensogl_core::data::color;
use ensogl_core::display;
use ensogl_core::display::shape::*;
use ensogl_core::display::traits::*;
use ensogl_core::display;
use ensogl_text as text;
use ensogl_theme::component::label as theme;
@ -64,22 +64,22 @@ ensogl_core::define_endpoints! {
// === Model ===
// =============
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
struct Model {
background : background::View,
label : text::Area,
display_object : display::object::Instance,
style : StyleWatch,
background: background::View,
label: text::Area,
display_object: display::object::Instance,
style: StyleWatch,
}
impl Model {
fn new(app: Application) -> Self {
let app = app.clone_ref();
let scene = app.display.scene();
let logger = Logger::new("TextLabel");
let app = app.clone_ref();
let scene = app.display.scene();
let logger = Logger::new("TextLabel");
let display_object = display::object::Instance::new(&logger);
let label = app.new_view::<text::Area>();
let background = background::View::new(&logger);
let label = app.new_view::<text::Area>();
let background = background::View::new(&logger);
// FIXME[MM/WD]: Depth sorting of labels to in front of everything else in the scene.
// Temporary solution. The depth management needs to allow defining relative position of
@ -93,44 +93,44 @@ impl Model {
let style = StyleWatch::new(&app.display.scene().style_sheet);
Model {background,label,display_object,style}
Model { background, label, display_object, style }
}
pub fn height(&self) -> f32 {
self.style.get_number(theme::height)
}
fn set_width(&self, width:f32) -> Vector2 {
let padding_outer = self.style.get_number(theme::padding_outer);
fn set_width(&self, width: f32) -> Vector2 {
let padding_outer = self.style.get_number(theme::padding_outer);
let padding_inner_x = self.style.get_number(theme::padding_inner_x);
let padding_inner_y = self.style.get_number(theme::padding_inner_y);
let padding_x = padding_outer + padding_inner_x;
let padding_y = padding_outer + padding_inner_y;
let padding = Vector2(padding_x,padding_y);
let text_size = self.style.get_number(theme::text::size);
let text_offset = self.style.get_number(theme::text::offset);
let height = self.height();
let size = Vector2(width,height);
let padded_size = size + padding * 2.0;
let padding_x = padding_outer + padding_inner_x;
let padding_y = padding_outer + padding_inner_y;
let padding = Vector2(padding_x, padding_y);
let text_size = self.style.get_number(theme::text::size);
let text_offset = self.style.get_number(theme::text::offset);
let height = self.height();
let size = Vector2(width, height);
let padded_size = size + padding * 2.0;
self.background.size.set(padded_size);
let text_origin = Vector2(text_offset - size.x/2.0, text_size /2.0);
let text_origin = Vector2(text_offset - size.x / 2.0, text_size / 2.0);
self.label.set_position_xy(text_origin);
padded_size
}
fn set_content(&self, t:&str) -> Vector2 {
fn set_content(&self, t: &str) -> Vector2 {
self.label.set_content(t);
self.set_width(self.label.width.value())
}
fn set_opacity(&self, value:f32) {
fn set_opacity(&self, value: f32) {
let text_color_path = theme::text;
let text_color = self.style.get_color(text_color_path).multiply_alpha(value);
let text_color = self.style.get_color(text_color_path).multiply_alpha(value);
self.label.frp.set_color_all.emit(text_color);
self.label.frp.set_default_color.emit(text_color);
let bg_color_path = theme::background;
let bg_color = self.style.get_color(bg_color_path).multiply_alpha(value);
let bg_color = self.style.get_color(bg_color_path).multiply_alpha(value);
self.background.bg_color.set(bg_color.into())
}
}
@ -142,24 +142,24 @@ impl Model {
// =======================
#[allow(missing_docs)]
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
pub struct Label {
model : Rc<Model>,
pub frp : Rc<Frp>,
model: Rc<Model>,
pub frp: Rc<Frp>,
}
impl Label {
/// Constructor.
pub fn new(app:&Application) -> Self {
let frp = Rc::new(Frp::new());
pub fn new(app: &Application) -> Self {
let frp = Rc::new(Frp::new());
let model = Rc::new(Model::new(app.clone_ref()));
Label {model,frp}.init()
Label { model, frp }.init()
}
fn init(self) -> Self {
let frp = &self.frp;
let frp = &self.frp;
let network = &frp.network;
let model = &self.model;
let model = &self.model;
frp::extend! { network
frp.source.size <+ frp.set_content.map(f!((t)
@ -174,5 +174,7 @@ impl Label {
}
impl display::Object for Label {
fn display_object(&self) -> &display::object::Instance { &self.model.display_object }
fn display_object(&self) -> &display::object::Instance {
&self.model.display_object
}
}

View File

@ -4,7 +4,6 @@
#![feature(option_result_contains)]
#![feature(trait_alias)]
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
@ -13,8 +12,7 @@
#![warn(unsafe_code)]
#![warn(unused_import_braces)]
#![warn(unused_qualifications)]
#![recursion_limit="512"]
#![recursion_limit = "512"]
pub mod component;
pub mod drop_down_menu;

View File

@ -10,8 +10,8 @@ use crate::shadow;
use enso_frp as frp;
use ensogl_core::application;
use ensogl_core::application::Application;
use ensogl_core::application::shortcut;
use ensogl_core::application::Application;
use ensogl_core::display;
use ensogl_core::display::scene::layer::LayerId;
use ensogl_core::display::shape::*;
@ -29,8 +29,8 @@ pub use entry::Entry;
// === Constants ===
/// 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;
pub const SHADOW_PX: f32 = 10.0;
const SHAPE_PADDING: f32 = 5.0;
// === Selection ===
@ -40,7 +40,7 @@ pub mod selection {
use super::*;
/// The corner radius in pixels.
pub const CORNER_RADIUS_PX:f32 = 12.0;
pub const CORNER_RADIUS_PX: f32 = 12.0;
ensogl_core::define_shape_system! {
(style:Style) {
@ -66,7 +66,7 @@ pub mod background {
use super::*;
/// The corner radius in pixels.
pub const CORNER_RADIUS_PX:f32 = selection::CORNER_RADIUS_PX;
pub const CORNER_RADIUS_PX: f32 = selection::CORNER_RADIUS_PX;
ensogl_core::define_shape_system! {
below = [selection];
@ -93,100 +93,105 @@ pub mod background {
// =============
/// Information about displayed fragment of entries list.
#[derive(Copy,Clone,Debug,Default)]
#[derive(Copy, Clone, Debug, Default)]
struct View {
position_y : f32,
size : Vector2<f32>,
position_y: f32,
size: Vector2<f32>,
}
/// The Model of Select Component.
#[derive(Clone,CloneRef,Debug)]
struct Model<E:Entry> {
app : Application,
entries : entry::List<E>,
selection : selection::View,
background : background::View,
scrolled_area : display::object::Instance,
display_object : display::object::Instance,
#[derive(Clone, CloneRef, Debug)]
struct Model<E: Entry> {
app: Application,
entries: entry::List<E>,
selection: selection::View,
background: background::View,
scrolled_area: display::object::Instance,
display_object: display::object::Instance,
}
impl<E:Entry> Model<E> {
fn new(app:&Application) -> Self {
let app = app.clone_ref();
let logger = Logger::new("SelectionContainer");
impl<E: Entry> Model<E> {
fn new(app: &Application) -> Self {
let app = app.clone_ref();
let logger = Logger::new("SelectionContainer");
let display_object = display::object::Instance::new(&logger);
let scrolled_area = display::object::Instance::new(&logger);
let entries = entry::List::new(&logger,&app);
let background = background::View::new(&logger);
let selection = selection::View::new(&logger);
let scrolled_area = display::object::Instance::new(&logger);
let entries = entry::List::new(&logger, &app);
let background = background::View::new(&logger);
let selection = selection::View::new(&logger);
display_object.add_child(&background);
display_object.add_child(&scrolled_area);
scrolled_area.add_child(&entries);
scrolled_area.add_child(&selection);
Model{app,entries,selection,background,scrolled_area,display_object}
Model { app, entries, selection, background, scrolled_area, display_object }
}
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.scene().style_sheet);
let styles = StyleWatch::new(&self.app.display.scene().style_sheet);
styles.get_number(ensogl_theme::application::searcher::padding)
}
/// Update the displayed entries list when _view_ has changed - the list was scrolled or
/// resized.
fn update_after_view_change(&self, view:&View) {
let visible_entries = Self::visible_entries(view,self.entries.entry_count());
let padding_px = self.padding();
let padding = 2.0 * padding_px + SHAPE_PADDING;
let padding = Vector2(padding, padding);
let shadow = Vector2(2.0 * SHADOW_PX, 2.0 * SHADOW_PX);
fn update_after_view_change(&self, view: &View) {
let visible_entries = Self::visible_entries(view, self.entries.entry_count());
let padding_px = self.padding();
let padding = 2.0 * padding_px + SHAPE_PADDING;
let padding = Vector2(padding, padding);
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);
self.scrolled_area.set_position_y(view.size.y / 2.0 - view.position_y);
self.entries.update_entries(visible_entries);
}
fn set_entries(&self, provider:entry::AnyModelProvider<E>, view:&View) {
let visible_entries = Self::visible_entries(view,provider.entry_count());
self.entries.update_entries_new_provider(provider,visible_entries);
fn set_entries(&self, provider: entry::AnyModelProvider<E>, view: &View) {
let visible_entries = Self::visible_entries(view, provider.entry_count());
self.entries.update_entries_new_provider(provider, visible_entries);
}
fn visible_entries(View {position_y,size}:&View, entry_count:usize) -> Range<entry::Id> {
fn visible_entries(View { position_y, size }: &View, entry_count: usize) -> Range<entry::Id> {
if entry_count == 0 {
0..0
} else {
let entry_at_y_saturating = |y:f32| {
match entry::List::<E>::entry_at_y_position(y,entry_count) {
let entry_at_y_saturating =
|y: f32| match entry::List::<E>::entry_at_y_position(y, entry_count) {
entry::list::IdAtYPosition::AboveFirst => 0,
entry::list::IdAtYPosition::UnderLast => entry_count - 1,
entry::list::IdAtYPosition::Entry(id) => id,
}
};
entry::list::IdAtYPosition::UnderLast => entry_count - 1,
entry::list::IdAtYPosition::Entry(id) => id,
};
let first = entry_at_y_saturating(*position_y);
let last = entry_at_y_saturating(position_y - size.y) + 1;
let last = entry_at_y_saturating(position_y - size.y) + 1;
first..last
}
}
/// Check if the `point` is inside component assuming that it have given `size`.
fn is_inside(&self, point:Vector2<f32>, size:Vector2<f32>) -> bool {
let pos_obj_space = self.app.display.scene().screen_to_object_space(&self.background,point);
let x_range = (-size.x / 2.0)..=(size.x / 2.0);
let y_range = (-size.y / 2.0)..=(size.y / 2.0);
fn is_inside(&self, point: Vector2<f32>, size: Vector2<f32>) -> bool {
let pos_obj_space =
self.app.display.scene().screen_to_object_space(&self.background, point);
let x_range = (-size.x / 2.0)..=(size.x / 2.0);
let y_range = (-size.y / 2.0)..=(size.y / 2.0);
x_range.contains(&pos_obj_space.x) && y_range.contains(&pos_obj_space.y)
}
fn selected_entry_after_jump
(&self, current_entry:Option<entry::Id>, jump:isize) -> Option<entry::Id> {
fn selected_entry_after_jump(
&self,
current_entry: Option<entry::Id>,
jump: isize,
) -> Option<entry::Id> {
if jump < 0 {
let current_entry = current_entry?;
if current_entry == 0 { None }
else { Some(current_entry.saturating_sub(-jump as usize)) }
if current_entry == 0 {
None
} else {
Some(current_entry.saturating_sub(-jump as usize))
}
} else {
let max_entry = self.entries.entry_count().checked_sub(1)?;
Some(current_entry.map_or(0, |id| id+(jump as usize)).min(max_entry))
Some(current_entry.map_or(0, |id| id + (jump as usize)).min(max_entry))
}
}
}
@ -243,40 +248,43 @@ ensogl_core::define_endpoints! {
/// This is a displayed list of entries (of any type `E`) with possibility of selecting one and
/// "choosing" by clicking or pressing enter. The basic entry types are defined in [`entry`] module.
#[allow(missing_docs)]
#[derive(Clone,CloneRef,Debug)]
pub struct ListView<E:Entry> {
model : Model<E>,
pub frp : Frp<E>,
#[derive(Clone, CloneRef, Debug)]
pub struct ListView<E: Entry> {
model: Model<E>,
pub frp: Frp<E>,
}
impl<E:Entry> Deref for ListView<E> {
impl<E: Entry> Deref for ListView<E> {
type Target = Frp<E>;
fn deref(&self) -> &Self::Target { &self.frp }
fn deref(&self) -> &Self::Target {
&self.frp
}
}
impl<E:Entry> ListView<E>
where E::Model : Default {
impl<E: Entry> ListView<E>
where E::Model: Default
{
/// Constructor.
pub fn new(app:&Application) -> Self {
let frp = Frp::new();
pub fn new(app: &Application) -> Self {
let frp = Frp::new();
let model = Model::new(app);
ListView {model,frp}.init(app)
ListView { model, 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;
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;
let network = &frp.network;
let model = &self.model;
let scene = app.display.scene();
let mouse = &scene.mouse.frp;
let view_y = DEPRECATED_Animation::<f32>::new(network);
let selection_y = DEPRECATED_Animation::<f32>::new(network);
let frp = &self.frp;
let network = &frp.network;
let model = &self.model;
let scene = app.display.scene();
let mouse = &scene.mouse.frp;
let view_y = DEPRECATED_Animation::<f32>::new(network);
let selection_y = DEPRECATED_Animation::<f32>::new(network);
let selection_height = DEPRECATED_Animation::<f32>::new(network);
frp::extend!{ network
frp::extend! { network
// === Mouse Position ===
@ -424,32 +432,46 @@ where E::Model : Default {
}
/// Sets the scene layer where the labels will be placed.
pub fn set_label_layer(&self, layer:LayerId) {
pub fn set_label_layer(&self, layer: LayerId) {
self.model.entries.set_label_layer(layer);
}
}
impl<E:Entry> display::Object for ListView<E> {
fn display_object(&self) -> &display::object::Instance { &self.model.display_object }
}
impl<E:Entry> application::command::FrpNetworkProvider for ListView<E> {
fn network(&self) -> &frp::Network { &self.frp.network }
}
impl<E:Entry> application::View for ListView<E> {
fn label() -> &'static str { "ListView" }
fn new(app:&Application) -> Self { ListView::new(app) }
fn app(&self) -> &Application { &self.model.app }
fn default_shortcuts() -> Vec<shortcut::Shortcut> {
use shortcut::ActionType::*;
(&[ (PressAndRepeat , "up" , "move_selection_up")
, (PressAndRepeat , "down" , "move_selection_down")
, (Press , "page-up" , "move_selection_page_up")
, (Press , "page-down" , "move_selection_page_down")
, (Press , "home" , "move_selection_to_first")
, (Press , "end" , "move_selection_to_last")
, (Press , "enter" , "chose_selected_entry")
]).iter().map(|(a,b,c)|Self::self_shortcut(*a,*b,*c)).collect()
impl<E: Entry> display::Object for ListView<E> {
fn display_object(&self) -> &display::object::Instance {
&self.model.display_object
}
}
impl<E: Entry> application::command::FrpNetworkProvider for ListView<E> {
fn network(&self) -> &frp::Network {
&self.frp.network
}
}
impl<E: Entry> application::View for ListView<E> {
fn label() -> &'static str {
"ListView"
}
fn new(app: &Application) -> Self {
ListView::new(app)
}
fn app(&self) -> &Application {
&self.model.app
}
fn default_shortcuts() -> Vec<shortcut::Shortcut> {
use shortcut::ActionType::*;
(&[
(PressAndRepeat, "up", "move_selection_up"),
(PressAndRepeat, "down", "move_selection_down"),
(Press, "page-up", "move_selection_page_up"),
(Press, "page-down", "move_selection_page_down"),
(Press, "home", "move_selection_to_first"),
(Press, "end", "move_selection_to_last"),
(Press, "enter", "chose_selected_entry"),
])
.iter()
.map(|(a, b, c)| Self::self_shortcut(*a, *b, *c))
.collect()
}
}

View File

@ -17,9 +17,9 @@ use ensogl_theme as theme;
// =================
/// Padding inside entry in pixels.
pub const PADDING:f32 = 14.0;
pub const PADDING: f32 = 14.0;
/// The overall entry's height (including padding).
pub const HEIGHT:f32 = 30.0;
pub const HEIGHT: f32 = 30.0;
@ -52,18 +52,18 @@ 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
/// be displayed.
type Model : Debug + Default;
type Model: Debug + Default;
/// An Object constructor.
fn new(app:&Application) -> Self;
fn new(app: &Application) -> Self;
/// Update content with new model.
fn update(&self, model:&Self::Model);
fn update(&self, model: &Self::Model);
/// Set the layer of all [`text::Area`] components inside. The [`text::Area`] component is
/// handled in a special way, and is often in different layer than shapes. See TODO comment
/// in [`text::Area::add_to_scene_layer`] method.
fn set_label_layer(&self, label_layer:&display::scene::Layer);
fn set_label_layer(&self, label_layer: &display::scene::Layer);
}
@ -74,25 +74,25 @@ pub trait Entry: CloneRef + Debug + display::Object + 'static {
// === Label ===
/// The [`Entry`] being a single text field displaying String.
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
pub struct Label {
display_object : display::object::Instance,
label : text::Area,
network : enso_frp::Network,
style_watch : StyleWatchFrp,
display_object: display::object::Instance,
label: text::Area,
network: enso_frp::Network,
style_watch: StyleWatchFrp,
}
impl Entry for Label {
type Model = String;
fn new(app: &Application) -> Self {
let logger = Logger::new("list_view::entry::Label");
let logger = Logger::new("list_view::entry::Label");
let display_object = display::object::Instance::new(logger);
let label = app.new_view::<ensogl_text::Area>();
let network = frp::Network::new("list_view::entry::Label");
let style_watch = StyleWatchFrp::new(&app.display.scene().style_sheet);
let color = style_watch.get_color(theme::widget::list_view::text);
let size = style_watch.get_number(theme::widget::list_view::text::size);
let label = app.new_view::<ensogl_text::Area>();
let network = frp::Network::new("list_view::entry::Label");
let style_watch = StyleWatchFrp::new(&app.display.scene().style_sheet);
let color = style_watch.get_color(theme::widget::list_view::text);
let size = style_watch.get_number(theme::widget::list_view::text::size);
display_object.add_child(&label);
frp::extend! { network
@ -105,20 +105,22 @@ impl Entry for Label {
eval size ((size) label.set_position_y(size/2.0));
}
init.emit(());
Self {display_object,label,network,style_watch}
Self { display_object, label, network, style_watch }
}
fn update(&self, model: &Self::Model) {
self.label.set_content(model);
}
fn set_label_layer(&self, label_layer:&display::scene::Layer) {
fn set_label_layer(&self, label_layer: &display::scene::Layer) {
self.label.add_to_scene_layer(label_layer);
}
}
impl display::Object for Label {
fn display_object(&self) -> &display::object::Instance { &self.display_object }
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}
@ -126,29 +128,30 @@ impl display::Object for Label {
/// The model for [`HighlightedLabel`], being an entry displayed as a single label with highlighted
/// some parts of text.
#[derive(Clone,Debug,Default)]
#[derive(Clone, Debug, Default)]
pub struct GlyphHighlightedLabelModel {
/// Displayed text.
pub label:String,
pub label: String,
/// A list of ranges of highlighted bytes.
pub highlighted:Vec<text::Range<text::Bytes>>,
pub highlighted: Vec<text::Range<text::Bytes>>,
}
/// The [`Entry`] similar to the [`Label`], but allows highlighting some parts of text.
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
pub struct GlyphHighlightedLabel {
inner : Label,
highlight : frp::Source<Vec<text::Range<text::Bytes>>>,
inner: Label,
highlight: frp::Source<Vec<text::Range<text::Bytes>>>,
}
impl Entry for GlyphHighlightedLabel {
type Model = GlyphHighlightedLabelModel;
fn new(app: &Application) -> Self {
let inner = Label::new(app);
let network = &inner.network;
let highlight_color = inner.style_watch.get_color(theme::widget::list_view::text::highlight);
let label = &inner.label;
let inner = Label::new(app);
let network = &inner.network;
let highlight_color =
inner.style_watch.get_color(theme::widget::list_view::text::highlight);
let label = &inner.label;
frp::extend! { network
highlight <- source::<Vec<text::Range<text::Bytes>>>();
@ -159,7 +162,7 @@ impl Entry for GlyphHighlightedLabel {
}
});
}
Self {inner,highlight}
Self { inner, highlight }
}
fn update(&self, model: &Self::Model) {
@ -167,13 +170,15 @@ impl Entry for GlyphHighlightedLabel {
self.highlight.emit(&model.highlighted);
}
fn set_label_layer(&self, layer:&display::scene::Layer) {
fn set_label_layer(&self, layer: &display::scene::Layer) {
self.inner.set_label_layer(layer);
}
}
impl display::Object for GlyphHighlightedLabel {
fn display_object(&self) -> &display::object::Instance { self.inner.display_object() }
fn display_object(&self) -> &display::object::Instance {
self.inner.display_object()
}
}
@ -189,37 +194,51 @@ impl display::Object for GlyphHighlightedLabel {
/// The [`crate::ListView`] component does not display all entries at once, instead it lazily ask
/// for models of entries when they're about to be displayed. So setting the select content is
/// essentially providing an implementor of this trait.
pub trait ModelProvider<E> : Debug {
pub trait ModelProvider<E>: Debug {
/// Number of all entries.
fn entry_count(&self) -> usize;
/// Get the model of entry with given id. The implementors should return `None` only when
/// requested id greater or equal to entries count.
fn get(&self, id:Id) -> Option<E::Model>
where E : Entry;
fn get(&self, id: Id) -> Option<E::Model>
where E: Entry;
}
// === AnyModelProvider ===
/// A wrapper for shared instance of some Provider of models for `E` entries.
#[derive(Debug,Shrinkwrap)]
#[derive(Debug, Shrinkwrap)]
pub struct AnyModelProvider<E>(Rc<dyn ModelProvider<E>>);
impl<E> Clone for AnyModelProvider<E> { fn clone (&self) -> Self { Self(self.0.clone()) }}
impl<E> CloneRef for AnyModelProvider<E> { fn clone_ref(&self) -> Self { Self(self.0.clone_ref()) }}
impl<E> Clone for AnyModelProvider<E> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<E> CloneRef for AnyModelProvider<E> {
fn clone_ref(&self) -> Self {
Self(self.0.clone_ref())
}
}
impl<E> AnyModelProvider<E> {
/// Create from typed provider.
pub fn new<T:ModelProvider<E>+'static>(provider:T) -> Self { Self(Rc::new(provider)) }
pub fn new<T: ModelProvider<E> + 'static>(provider: T) -> Self {
Self(Rc::new(provider))
}
}
impl<E, T:ModelProvider<E>+'static> From<Rc<T>> for AnyModelProvider<E> {
fn from(provider:Rc<T>) -> Self { Self(provider) }
impl<E, T: ModelProvider<E> + 'static> From<Rc<T>> for AnyModelProvider<E> {
fn from(provider: Rc<T>) -> Self {
Self(provider)
}
}
impl<E> Default for AnyModelProvider<E> {
fn default() -> Self { Self::new(EmptyProvider) }
fn default() -> Self {
Self::new(EmptyProvider)
}
}
@ -228,26 +247,33 @@ impl<E> Default for AnyModelProvider<E> {
/// An Entry Model Provider giving no entries.
///
/// This is the default provider for new select components.
#[derive(Clone,CloneRef,Copy,Debug)]
#[derive(Clone, CloneRef, Copy, Debug)]
pub struct EmptyProvider;
impl<E> ModelProvider<E> for EmptyProvider {
fn entry_count(&self) -> usize { 0 }
fn get (&self, _:usize) -> Option<E::Model> where E : Entry { None }
fn entry_count(&self) -> usize {
0
}
fn get(&self, _: usize) -> Option<E::Model>
where E: Entry {
None
}
}
// === ModelProvider for Vectors ===
impl<E,T> ModelProvider<E> for Vec<T>
where E : Entry,
T : Debug + Clone + Into<E::Model> {
impl<E, T> ModelProvider<E> for Vec<T>
where
E: Entry,
T: Debug + Clone + Into<E::Model>,
{
fn entry_count(&self) -> usize {
self.len()
}
fn get(&self, id:usize) -> Option<E::Model> {
Some(<[T]>::get(self, id)?.clone().into())
fn get(&self, id: usize) -> Option<E::Model> {
Some(<[T]>::get(self, id)?.clone().into())
}
}
@ -255,29 +281,28 @@ where E : Entry,
// === SingleMaskedProvider ===
/// An Entry Model Provider that wraps a `AnyModelProvider` and allows the masking of a single item.
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
pub struct SingleMaskedProvider<E> {
content : AnyModelProvider<E>,
mask : Cell<Option<Id>>,
content: AnyModelProvider<E>,
mask: Cell<Option<Id>>,
}
impl<E:Debug> ModelProvider<E> for SingleMaskedProvider<E> {
impl<E: Debug> ModelProvider<E> for SingleMaskedProvider<E> {
fn entry_count(&self) -> usize {
match self.mask.get() {
None => self.content.entry_count(),
None => self.content.entry_count(),
Some(_) => self.content.entry_count().saturating_sub(1),
}
}
fn get(&self, ix:usize) -> Option<E::Model>
where E : Entry {
fn get(&self, ix: usize) -> Option<E::Model>
where E: Entry {
let internal_ix = self.unmasked_index(ix);
self.content.get(internal_ix)
}
}
impl<E> SingleMaskedProvider<E> {
/// Return the index to the unmasked underlying data. Will only be valid to use after
/// calling `clear_mask`.
///
@ -294,11 +319,11 @@ impl<E> SingleMaskedProvider<E> {
/// Masked indices [0, 1, 2, 3]
/// Unmasked Index [0, 1, 2, 3]
/// ```
pub fn unmasked_index(&self, ix:Id) -> Id {
pub fn unmasked_index(&self, ix: Id) -> Id {
match self.mask.get() {
None => ix,
Some(id) if ix < id => ix,
Some(_) => ix+1,
None => ix,
Some(id) if ix < id => ix,
Some(_) => ix + 1,
}
}
@ -306,7 +331,7 @@ impl<E> SingleMaskedProvider<E> {
/// will behave as if it was not there.
///
/// *Important:* The index is interpreted according to the _masked_ position of elements.
pub fn set_mask(&self, ix:Id) {
pub fn set_mask(&self, ix: Id) {
let internal_ix = self.unmasked_index(ix);
self.mask.set(Some(internal_ix));
}
@ -315,7 +340,7 @@ impl<E> SingleMaskedProvider<E> {
/// will behave as if it was not there.
///
/// *Important:* The index is interpreted according to the _unmasked_ position of elements.
pub fn set_mask_raw(&self, ix:Id) {
pub fn set_mask_raw(&self, ix: Id) {
self.mask.set(Some(ix));
}
@ -326,9 +351,9 @@ impl<E> SingleMaskedProvider<E> {
}
impl<E> From<AnyModelProvider<E>> for SingleMaskedProvider<E> {
fn from(content:AnyModelProvider<E>) -> Self {
fn from(content: AnyModelProvider<E>) -> Self {
let mask = default();
SingleMaskedProvider{content,mask}
SingleMaskedProvider { content, mask }
}
}
@ -344,10 +369,10 @@ mod tests {
#[test]
fn test_masked_provider() {
let test_data = vec!["A", "B", "C", "D"];
let test_data = vec!["A", "B", "C", "D"];
let test_models = test_data.into_iter().map(|label| label.to_owned()).collect_vec();
let provider = AnyModelProvider::<Label>::new(test_models);
let provider:SingleMaskedProvider<Label> = provider.into();
let provider = AnyModelProvider::<Label>::new(test_models);
let provider: SingleMaskedProvider<Label> = provider.into();
assert_eq!(provider.entry_count(), 4);
assert_eq!(provider.get(0).unwrap(), "A");

View File

@ -21,10 +21,10 @@ use ensogl_core::display::scene::layer::LayerId;
/// 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.
#[allow(missing_docs)]
#[derive(Clone,CloneRef,Debug)]
pub struct DisplayedEntry<E:CloneRef> {
pub id : Rc<Cell<Option<entry::Id>>>,
pub entry : E,
#[derive(Clone, CloneRef, Debug)]
pub struct DisplayedEntry<E: CloneRef> {
pub id: Rc<Cell<Option<entry::Id>>>,
pub entry: E,
}
@ -35,45 +35,51 @@ pub struct DisplayedEntry<E:CloneRef> {
/// The output of `entry_at_y_position`
#[allow(missing_docs)]
#[derive(Copy,Clone,Debug,Eq,Hash,PartialEq)]
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum IdAtYPosition {
AboveFirst, UnderLast, Entry(entry::Id)
AboveFirst,
UnderLast,
Entry(entry::Id),
}
impl IdAtYPosition {
/// Returns id of entry if present.
pub fn entry(&self) -> Option<entry::Id> {
if let Self::Entry(id) = self { Some(*id) }
else { None }
if let Self::Entry(id) = self {
Some(*id)
} else {
None
}
}
}
/// A view containing an entry list, arranged in column.
///
/// Not all entries are displayed at once, only those visible.
#[derive(Clone,CloneRef,Debug)]
pub struct List<E:CloneRef> {
logger : Logger,
app : Application,
display_object : display::object::Instance,
entries : Rc<RefCell<Vec<DisplayedEntry<E>>>>,
entries_range : Rc<CloneCell<Range<entry::Id>>>,
provider : Rc<CloneRefCell<entry::AnyModelProvider<E>>>,
label_layer : Rc<Cell<LayerId>>,
#[derive(Clone, CloneRef, Debug)]
pub struct List<E: CloneRef> {
logger: Logger,
app: Application,
display_object: display::object::Instance,
entries: Rc<RefCell<Vec<DisplayedEntry<E>>>>,
entries_range: Rc<CloneCell<Range<entry::Id>>>,
provider: Rc<CloneRefCell<entry::AnyModelProvider<E>>>,
label_layer: Rc<Cell<LayerId>>,
}
impl<E: Entry> List<E>
where E::Model : Default {
where E::Model: Default
{
/// Entry List View constructor.
pub fn new(parent:impl AnyLogger, app:&Application) -> Self {
let app = app.clone_ref();
let logger = Logger::new_sub(parent,"entry::List");
let entries = default();
let entries_range = Rc::new(CloneCell::new(default()..default()));
pub fn new(parent: impl AnyLogger, app: &Application) -> Self {
let app = app.clone_ref();
let logger = Logger::new_sub(parent, "entry::List");
let entries = default();
let entries_range = Rc::new(CloneCell::new(default()..default()));
let display_object = display::object::Instance::new(&logger);
let provider = default();
let label_layer = Rc::new(Cell::new(app.display.scene().layers.label.id()));
List {logger,app,display_object,entries,entries_range,provider,label_layer}
let provider = default();
let label_layer = Rc::new(Cell::new(app.display.scene().layers.label.id()));
List { logger, app, display_object, entries, entries_range, provider, label_layer }
}
/// The number of all entries in List, including not displayed.
@ -87,16 +93,18 @@ where E::Model : Default {
}
/// Y position of entry with given id, relative to Entry List position.
pub fn position_y_of_entry(id:entry::Id) -> f32 { id as f32 * -entry::HEIGHT }
pub fn position_y_of_entry(id: entry::Id) -> f32 {
id as f32 * -entry::HEIGHT
}
/// Y range of entry with given id, relative to Entry List position.
pub fn y_range_of_entry(id:entry::Id) -> Range<f32> {
pub fn y_range_of_entry(id: entry::Id) -> Range<f32> {
let position = Self::position_y_of_entry(id);
(position - entry::HEIGHT / 2.0)..(position + entry::HEIGHT / 2.0)
}
/// Y range of all entries in this list, including not displayed.
pub fn y_range_of_all_entries(entry_count:usize) -> Range<f32> {
pub fn y_range_of_all_entries(entry_count: usize) -> Range<f32> {
let start = if entry_count > 0 {
Self::position_y_of_entry(entry_count - 1) - entry::HEIGHT / 2.0
} else {
@ -107,33 +115,39 @@ where E::Model : Default {
}
/// Get the entry id which lays on given y coordinate.
pub fn entry_at_y_position(y:f32, entry_count:usize) -> IdAtYPosition {
pub fn entry_at_y_position(y: f32, entry_count: usize) -> IdAtYPosition {
use IdAtYPosition::*;
let all_entries_start = Self::y_range_of_all_entries(entry_count).start;
if y > entry::HEIGHT/2.0 { AboveFirst }
else if y < all_entries_start { UnderLast }
else { Entry((-y/entry::HEIGHT + 0.5) as entry::Id) }
if y > entry::HEIGHT / 2.0 {
AboveFirst
} else if y < all_entries_start {
UnderLast
} else {
Entry((-y / entry::HEIGHT + 0.5) as entry::Id)
}
}
/// Update displayed entries to show the given range.
pub fn update_entries(&self, mut range:Range<entry::Id>) {
pub fn update_entries(&self, mut range: Range<entry::Id>) {
range.end = range.end.min(self.provider.get().entry_count());
if range != self.entries_range.get() {
debug!(self.logger, "Update entries for {range:?}");
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());
entries.iter().filter_map(|entry| entry.id.get()).collect()
});
let current_entries: HashSet<entry::Id> =
with(self.entries.borrow_mut(), |mut entries| {
entries.resize_with(range.len(), || self.create_new_entry());
entries.iter().filter_map(|entry| entry.id.get()).collect()
});
let missing = range.clone().filter(|id| !current_entries.contains(id));
// The provider is provided by user, so we should not keep any borrow when calling its
// methods.
let models = missing.map(|id| (id,provider.get(id)));
let models = missing.map(|id| (id, provider.get(id)));
with(self.entries.borrow(), |entries| {
let is_outdated = |e:&DisplayedEntry<E>| e.id.get().map_or(true, |i| !range.contains(&i));
let outdated = entries.iter().filter(|e| is_outdated(e));
for (entry,(id,model)) in outdated.zip(models) {
Self::update_entry(&self.logger,entry,id,&model);
let is_outdated =
|e: &DisplayedEntry<E>| e.id.get().map_or(true, |i| !range.contains(&i));
let outdated = entries.iter().filter(|e| is_outdated(e));
for (entry, (id, model)) in outdated.zip(models) {
Self::update_entry(&self.logger, entry, id, &model);
}
});
self.entries_range.set(range);
@ -141,63 +155,80 @@ where E::Model : Default {
}
/// Update displayed entries, giving new provider.
pub fn update_entries_new_provider
(&self, provider:impl Into<entry::AnyModelProvider<E>> + 'static, mut range:Range<entry::Id>) {
const MAX_SAFE_ENTRIES_COUNT:usize = 1000;
pub fn update_entries_new_provider(
&self,
provider: impl Into<entry::AnyModelProvider<E>> + 'static,
mut range: Range<entry::Id>,
) {
const MAX_SAFE_ENTRIES_COUNT: usize = 1000;
let provider = provider.into();
if provider.entry_count() > MAX_SAFE_ENTRIES_COUNT {
error!(self.logger, "ListView entry count exceed {MAX_SAFE_ENTRIES_COUNT} - so big \
error!(
self.logger,
"ListView entry count exceed {MAX_SAFE_ENTRIES_COUNT} - so big \
number of entries can cause visual glitches, e.g. https://github.com/enso-org/ide/\
issues/757 or https://github.com/enso-org/ide/issues/758");
issues/757 or https://github.com/enso-org/ide/issues/758"
);
}
range.end = range.end.min(provider.entry_count());
let models = range.clone().map(|id| (id,provider.get(id)));
range.end = range.end.min(provider.entry_count());
let models = range.clone().map(|id| (id, provider.get(id)));
let mut entries = self.entries.borrow_mut();
entries.resize_with(range.len(),|| self.create_new_entry());
for (entry,(id,model)) in entries.iter().zip(models) {
Self::update_entry(&self.logger,entry,id,&model);
entries.resize_with(range.len(), || self.create_new_entry());
for (entry, (id, model)) in entries.iter().zip(models) {
Self::update_entry(&self.logger, entry, id, &model);
}
self.entries_range.set(range);
self.provider.set(provider);
}
/// Sets the scene layer where the labels will be placed.
pub fn set_label_layer(&self, label_layer:LayerId) {
pub fn set_label_layer(&self, label_layer: LayerId) {
if let Some(layer) = self.app.display.scene().layers.get_sublayer(self.label_layer.get()) {
for entry in &*self.entries.borrow() {
entry.entry.set_label_layer(&layer);
}
} else {
error!(self.logger, "Cannot set layer {label_layer:?} for labels: the layer does not \
exist in the scene");
error!(
self.logger,
"Cannot set layer {label_layer:?} for labels: the layer does not \
exist in the scene"
);
}
self.label_layer.set(label_layer);
}
fn create_new_entry(&self) -> DisplayedEntry<E> {
let layers = &self.app.display.scene().layers;
let layer = layers.get_sublayer(self.label_layer.get()).unwrap_or_else(|| {
error!(self.logger, "Cannot set layer {self.label_layer:?} for labels: the layer does \
not exist in the scene");
let layer = layers.get_sublayer(self.label_layer.get()).unwrap_or_else(|| {
error!(
self.logger,
"Cannot set layer {self.label_layer:?} for labels: the layer does \
not exist in the scene"
);
layers.main.clone_ref()
});
let entry = DisplayedEntry {
id : default(),
entry : E::new(&self.app)
};
let entry = DisplayedEntry { id: default(), entry: E::new(&self.app) };
entry.entry.set_label_layer(&layer);
entry.entry.set_position_x(entry::PADDING);
self.add_child(&entry.entry);
entry
}
fn update_entry(logger:&Logger, entry:&DisplayedEntry<E>, id:entry::Id, model:&Option<E::Model>) {
debug!(logger, "Setting new model {model:?} for entry {id}; \
old entry: {entry.id.get():?}.");
fn update_entry(
logger: &Logger,
entry: &DisplayedEntry<E>,
id: entry::Id,
model: &Option<E::Model>,
) {
debug!(
logger,
"Setting new model {model:?} for entry {id}; \
old entry: {entry.id.get():?}."
);
entry.id.set(Some(id));
match model {
Some(model) => entry.entry.update(model),
None => {
None => {
error!(logger, "Model provider didn't return model for id {id}.");
entry.entry.update(&default());
}
@ -206,6 +237,8 @@ where E::Model : Default {
}
}
impl<E:CloneRef> display::Object for List<E> {
fn display_object(&self) -> &display::object::Instance { &self.display_object }
impl<E: CloneRef> display::Object for List<E> {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}

View File

@ -6,11 +6,11 @@ use crate::scrollbar;
use crate::scrollbar::Scrollbar;
use enso_frp as frp;
use ensogl_core::application::Application;
use ensogl_core::control::callback;
use ensogl_core::control::io::mouse;
use ensogl_core::display;
use ensogl_core::display::object::ObjectOps;
use ensogl_core::application::Application;
use ensogl_core::control::io::mouse;
use ensogl_core::control::callback;
@ -55,16 +55,16 @@ ensogl_core::define_endpoints! {
/// left corner. All scroll coordinates describe the point of the `content` object at that corner.
/// The scrollbars are only active when the content is actually larger than the viewport on the
/// respective axis.
#[derive(Debug,Clone,CloneRef)]
#[derive(Debug, Clone, CloneRef)]
pub struct ScrollArea {
/// All objects that should be inside the scroll area and affected by the scrolling, have to be
/// added as children to `content`.
pub content : display::object::Instance,
display_object : display::object::Instance,
h_scrollbar : Scrollbar,
v_scrollbar : Scrollbar,
scroll_handler_handle : callback::Handle,
frp : Frp,
pub content: display::object::Instance,
display_object: display::object::Instance,
h_scrollbar: Scrollbar,
v_scrollbar: Scrollbar,
scroll_handler_handle: callback::Handle,
frp: Frp,
}
impl Deref for ScrollArea {
@ -83,9 +83,9 @@ impl display::Object for ScrollArea {
impl ScrollArea {
/// Create a new scroll area for use in the given application.
pub fn new(app:&Application) -> ScrollArea {
let scene = app.display.scene();
let logger = Logger::new("ScrollArea");
pub fn new(app: &Application) -> ScrollArea {
let scene = app.display.scene();
let logger = Logger::new("ScrollArea");
let display_object = display::object::Instance::new(&logger);
let content = display::object::Instance::new(&logger);
@ -98,7 +98,7 @@ impl ScrollArea {
display_object.add_child(&v_scrollbar);
v_scrollbar.set_rotation_z(-90.0_f32.to_radians());
let frp = Frp::new();
let frp = Frp::new();
let network = &frp.network;
frp::extend! { network
@ -147,7 +147,7 @@ impl ScrollArea {
hovering <- hovering.sampler();
}
let mouse_manager = &mouse.mouse_manager;
let mouse_manager = &mouse.mouse_manager;
let scroll_handler = f!([v_scrollbar,h_scrollbar](event:&mouse::OnWheel)
if hovering.value() {
h_scrollbar.scroll_by(event.delta_x() as f32);
@ -157,6 +157,6 @@ impl ScrollArea {
let scroll_handler_handle = mouse_manager.on_wheel.add(scroll_handler);
ScrollArea {content,display_object,h_scrollbar,v_scrollbar,scroll_handler_handle,frp}
ScrollArea { content, display_object, h_scrollbar, v_scrollbar, scroll_handler_handle, frp }
}
}

View File

@ -3,18 +3,18 @@
use crate::prelude::*;
use enso_frp as frp;
use ensogl_core::Animation;
use ensogl_core::application::Application;
use ensogl_core::application;
use ensogl_core::application::Application;
use ensogl_core::data::color;
use ensogl_core::display::shape::*;
use ensogl_core::display::shape::StyleWatchFrp;
use ensogl_core::display::shape::*;
use ensogl_core::Animation;
use ensogl_theme as theme;
use crate::component;
use crate::selector::Bounds;
use crate::selector::model::Model;
use crate::selector;
use crate::selector::model::Model;
use crate::selector::Bounds;
use ensogl_core::animation::delayed::DelayedAnimation;
@ -28,17 +28,17 @@ use ensogl_core::animation::delayed::DelayedAnimation;
// described at https://github.com/enso-org/ide/issues/1654.
/// Amount the scrollbar moves on a single click, relative to the viewport size.
const CLICK_JUMP_PERCENTAGE : f32 = 0.80;
const CLICK_JUMP_PERCENTAGE: f32 = 0.80;
/// Width of the scrollbar in px.
pub const WIDTH : f32 = 11.0;
pub const WIDTH: f32 = 11.0;
/// The amount of padding on each side inside the scrollbar.
const PADDING : f32 = 2.0;
const PADDING: f32 = 2.0;
/// The thumb will be displayed with at least this size to make it more visible and dragging easier.
const MIN_THUMB_SIZE : f32 = 12.0;
const MIN_THUMB_SIZE: f32 = 12.0;
/// After an animation, the thumb will be visible for this time, before it hides again.
const HIDE_DELAY : f32 = 1000.0;
const HIDE_DELAY: f32 = 1000.0;
const ERROR_MARGIN_FOR_ACTIVITY_DETECTION : f32 = 0.1;
const ERROR_MARGIN_FOR_ACTIVITY_DETECTION: f32 = 0.1;
@ -71,9 +71,13 @@ ensogl_core::define_endpoints! {
}
impl Frp {
fn compute_target_alpha
(&recently_active:&bool, &dragging:&bool, &cursor_distance:&f32, &thumb_size:&f32, &max:&f32)
-> f32 {
fn compute_target_alpha(
&recently_active: &bool,
&dragging: &bool,
&cursor_distance: &f32,
&thumb_size: &f32,
&max: &f32,
) -> f32 {
let thumb_fills_bar = thumb_size >= max;
if thumb_fills_bar {
0.0
@ -93,13 +97,13 @@ impl Frp {
}
impl component::Frp<Model> for Frp {
fn init(&self, app:&Application, model:&Model, style:&StyleWatchFrp){
let frp = &self;
let network = &frp.network;
let scene = app.display.scene();
let mouse = &scene.mouse.frp;
let thumb_position = Animation::new(network);
let thumb_color = color::Animation::new(network);
fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) {
let frp = &self;
let network = &frp.network;
let scene = app.display.scene();
let mouse = &scene.mouse.frp;
let thumb_position = Animation::new(network);
let thumb_color = color::Animation::new(network);
let activity_cool_off = DelayedAnimation::new(network);
activity_cool_off.frp.set_delay(HIDE_DELAY);
activity_cool_off.frp.set_duration(0.0);
@ -118,7 +122,7 @@ impl component::Frp<Model> for Frp {
model.set_padding(PADDING);
let default_color = style.get_color(theme::component::slider::track::color);
let hover_color = style.get_color(theme::component::slider::track::hover_color);
let hover_color = style.get_color(theme::component::slider::track::hover_color);
frp::extend! { network
@ -251,7 +255,7 @@ impl component::Frp<Model> for Frp {
frp.set_length(200.0);
frp.set_thumb_size(0.2);
frp.set_max(1.0);
init_mouse_position.emit(Vector2(f32::NAN,f32::NAN));
init_mouse_position.emit(Vector2(f32::NAN, f32::NAN));
init_color.emit(());
}
}
@ -274,10 +278,16 @@ impl component::Frp<Model> for Frp {
///
/// All operations related to the scroll position take as argument a number of pixels describing a
/// position or distance on the scrolled area. We call them scroll units.
pub type Scrollbar = crate::component::Component<Model,Frp>;
pub type Scrollbar = crate::component::Component<Model, Frp>;
impl application::View for Scrollbar {
fn label() -> &'static str { "Scrollbar" }
fn new(app:&Application) -> Self { Scrollbar::new(app) }
fn app(&self) -> &Application { &self.app }
fn label() -> &'static str {
"Scrollbar"
}
fn new(app: &Application) -> Self {
Scrollbar::new(app)
}
fn app(&self) -> &Application {
&self.app
}
}

View File

@ -37,12 +37,18 @@ use model::*;
/// background that corresponds to the value relative in the range, for example, 0.0 would be not
/// filled in, 128.0 would be about halfway filled in, and 128.0 would be completely filled in.
/// The value can be changed by clicking and dragging on the shape.
pub type NumberPicker = crate::component::Component<Model,number::Frp>;
pub type NumberPicker = crate::component::Component<Model, number::Frp>;
impl application::View for NumberPicker {
fn label() -> &'static str { "NumberPicker" }
fn new(app:&Application) -> Self { NumberPicker::new(app) }
fn app(&self) -> &Application { &self.app }
fn label() -> &'static str {
"NumberPicker"
}
fn new(app: &Application) -> Self {
NumberPicker::new(app)
}
fn app(&self) -> &Application {
&self.app
}
}
@ -60,10 +66,16 @@ impl application::View for NumberPicker {
/// would show the track covering the right half of the background. The selected range can be
/// changed by clicking and dragging the track, which changes the whole range, but preserves the
/// width, or the individual edges of the track which changes just the respective end of the range.
pub type NumberRangePicker = crate::component::Component<Model,range::Frp>;
pub type NumberRangePicker = crate::component::Component<Model, range::Frp>;
impl application::View for NumberRangePicker {
fn label() -> &'static str { "RangePicker" }
fn new(app:&Application) -> Self { NumberRangePicker::new(app) }
fn app(&self) -> &Application { &self.app }
fn label() -> &'static str {
"RangePicker"
}
fn new(app: &Application) -> Self {
NumberRangePicker::new(app)
}
fn app(&self) -> &Application {
&self.app
}
}

View File

@ -18,24 +18,24 @@ use core::option::Option::Some;
/// Bounds of a selection. This indicates the lowest and highest value that can be selected in a
/// selection component.
#[derive(Clone,Copy,Debug,Default)]
#[derive(Clone, Copy, Debug, Default)]
pub struct Bounds {
/// Start of the bounds interval (inclusive).
pub start : f32,
pub start: f32,
/// End of the bounds interval (inclusive).
pub end : f32,
pub end: f32,
}
impl Bounds {
/// Constructor.
pub fn new(start:f32,end:f32) -> Self {
Bounds{start,end}
pub fn new(start: f32, end: f32) -> Self {
Bounds { start, end }
}
/// Return the `Bound` with the lower bound as `start` and the upper bound as `end`.
pub fn sorted(self) -> Self {
if self.start > self.end {
Bounds{start:self.end,end:self.start}
Bounds { start: self.end, end: self.start }
} else {
self
}
@ -52,9 +52,9 @@ impl Bounds {
}
}
impl From<(f32,f32)> for Bounds {
fn from((start,end): (f32,f32)) -> Self {
Bounds{start,end}
impl From<(f32, f32)> for Bounds {
fn from((start, end): (f32, f32)) -> Self {
Bounds { start, end }
}
}
@ -64,9 +64,11 @@ impl From<(f32,f32)> for Bounds {
/// ```text
/// normalised <- all2(&value,&bounds).map(normalise_value);
/// ````
pub fn normalise_value((value,bounds):&(f32,Bounds)) -> f32 {
pub fn normalise_value((value, bounds): &(f32, Bounds)) -> f32 {
let width = bounds.width();
if width == 0.0 { return 0.0 }
if width == 0.0 {
return 0.0;
}
(value - bounds.start) / width
}
@ -77,7 +79,7 @@ pub fn normalise_value((value,bounds):&(f32,Bounds)) -> f32 {
/// ```text
/// value <- all(&bounds,&normalised).map(absolute_value);
/// ````
pub fn absolute_value((bounds,normalised_value):&(Bounds,f32)) -> f32 {
pub fn absolute_value((bounds, normalised_value): &(Bounds, f32)) -> f32 {
(normalised_value * bounds.width()) + bounds.start
}
@ -85,21 +87,23 @@ pub fn absolute_value((bounds,normalised_value):&(Bounds,f32)) -> f32 {
/// Note that the shape is centered on (0,0), thus half the width extends into the negative values.
/// For use in FRP `map` method, thus taking references.
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn position_to_normalised_value(pos:&Vector2,width:&f32) -> f32 {
if *width == 0.0 { return 0.0 }
pub fn position_to_normalised_value(pos: &Vector2, width: &f32) -> f32 {
if *width == 0.0 {
return 0.0;
}
((pos.x / (width / 2.0)) + 1.0) / 2.0
}
/// Check whether the given value is within the given bounds.
fn value_in_bounds(value:f32, bounds:Bounds) -> bool {
fn value_in_bounds(value: f32, bounds: Bounds) -> bool {
let bounds_sorted = bounds.sorted();
value >= bounds_sorted.start && value <= bounds_sorted.end
}
/// Check whether the given bounds are completely contained in the second bounds.
pub fn bounds_in_bounds(bounds_inner:Bounds, bounds_outer:Bounds) -> bool {
value_in_bounds(bounds_inner.start,bounds_outer)
&& value_in_bounds(bounds_inner.end,bounds_outer)
pub fn bounds_in_bounds(bounds_inner: Bounds, bounds_outer: Bounds) -> bool {
value_in_bounds(bounds_inner.start, bounds_outer)
&& value_in_bounds(bounds_inner.end, bounds_outer)
}
/// Clamp `value` to the `overflow_bounds`, or to [0, 1] if no bounds are given.
@ -110,11 +114,11 @@ pub fn bounds_in_bounds(bounds_inner:Bounds, bounds_outer:Bounds) -> bool {
/// clamped <- value_update.map2(&normalised_overflow_bounds,clamp_with_overflow);
/// ```
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn clamp_with_overflow(value:&f32, overflow_bounds:&Option<Bounds>) -> f32 {
if let Some(overflow_bounds) = overflow_bounds{
value.clamp(overflow_bounds.start,overflow_bounds.end)
pub fn clamp_with_overflow(value: &f32, overflow_bounds: &Option<Bounds>) -> f32 {
if let Some(overflow_bounds) = overflow_bounds {
value.clamp(overflow_bounds.start, overflow_bounds.end)
} else {
value.clamp(0.0,1.0)
value.clamp(0.0, 1.0)
}
}
@ -126,11 +130,11 @@ pub fn clamp_with_overflow(value:&f32, overflow_bounds:&Option<Bounds>) -> f32 {
/// is_in_bounds <- bounds_update.map2(&normalised_overflow_bounds,should_clamp_with_overflow);
/// ```
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn should_clamp_with_overflow(bounds:&Bounds, overflow_bounds:&Option<Bounds>) -> bool {
pub fn should_clamp_with_overflow(bounds: &Bounds, overflow_bounds: &Option<Bounds>) -> bool {
if let Some(overflow_bounds) = overflow_bounds {
bounds_in_bounds(*bounds,*overflow_bounds)
bounds_in_bounds(*bounds, *overflow_bounds)
} else {
bounds_in_bounds(*bounds,(0.0,1.0).into())
bounds_in_bounds(*bounds, (0.0, 1.0).into())
}
}
@ -149,229 +153,232 @@ mod tests {
#[test]
fn test_normalise_value() {
let test = |start,end,value,expected| {
let bounds = Bounds::new(start,end);
let normalised = normalise_value(&(value,bounds));
assert_float_eq!(normalised,expected,ulps<=7)
let test = |start, end, value, expected| {
let bounds = Bounds::new(start, end);
let normalised = normalise_value(&(value, bounds));
assert_float_eq!(normalised, expected, ulps <= 7)
};
test(0.0,1.0,0.0,0.0);
test(0.0,1.0,0.1,0.1);
test(0.0,1.0,0.2,0.2);
test(0.0,1.0,0.3,0.3);
test(0.0,1.0,0.4,0.4);
test(0.0,1.0,0.5,0.5);
test(0.0,1.0,0.6,0.6);
test(0.0,1.0,0.7,0.7);
test(0.0,1.0,0.8,0.8);
test(0.0,1.0,0.9,0.9);
test(0.0,1.0,1.0,1.0);
test(0.0, 1.0, 0.0, 0.0);
test(0.0, 1.0, 0.1, 0.1);
test(0.0, 1.0, 0.2, 0.2);
test(0.0, 1.0, 0.3, 0.3);
test(0.0, 1.0, 0.4, 0.4);
test(0.0, 1.0, 0.5, 0.5);
test(0.0, 1.0, 0.6, 0.6);
test(0.0, 1.0, 0.7, 0.7);
test(0.0, 1.0, 0.8, 0.8);
test(0.0, 1.0, 0.9, 0.9);
test(0.0, 1.0, 1.0, 1.0);
test(0.0,1.0,-2.0,-2.0);
test(0.0,1.0,-1.0,-1.0);
test(0.0,1.0,2.0,2.0);
test(0.0,1.0,3.0,3.0);
test(0.0, 1.0, -2.0, -2.0);
test(0.0, 1.0, -1.0, -1.0);
test(0.0, 1.0, 2.0, 2.0);
test(0.0, 1.0, 3.0, 3.0);
test(-1.0,1.0,-1.0,0.0);
test(-1.0,1.0,-0.5,0.25);
test(-1.0,1.0,0.0,0.5);
test(-1.0,1.0,0.5,0.75);
test(-1.0,1.0,1.0,1.0);
test(-1.0, 1.0, -1.0, 0.0);
test(-1.0, 1.0, -0.5, 0.25);
test(-1.0, 1.0, 0.0, 0.5);
test(-1.0, 1.0, 0.5, 0.75);
test(-1.0, 1.0, 1.0, 1.0);
test(1.0,-1.0,-1.0,1.0);
test(1.0,-1.0,-0.5,0.75);
test(1.0,-1.0,0.0,0.5);
test(1.0,-1.0,0.5,0.25);
test(1.0,-1.0,1.0,0.0);
test(1.0, -1.0, -1.0, 1.0);
test(1.0, -1.0, -0.5, 0.75);
test(1.0, -1.0, 0.0, 0.5);
test(1.0, -1.0, 0.5, 0.25);
test(1.0, -1.0, 1.0, 0.0);
test(-10.0,20.0,-10.0,0.0);
test(-10.0,20.0,20.0,1.0);
test(-10.0,20.0,0.0,0.33333333);
test(-10.0, 20.0, -10.0, 0.0);
test(-10.0, 20.0, 20.0, 1.0);
test(-10.0, 20.0, 0.0, 0.33333333);
test(-999999999.0,999999999.0,-999999999.0,0.0);
test(-999999999.0,999999999.0,0.0,0.5);
test(-999999999.0,999999999.0,999999999.0,1.0);
test(-999999999.0, 999999999.0, -999999999.0, 0.0);
test(-999999999.0, 999999999.0, 0.0, 0.5);
test(-999999999.0, 999999999.0, 999999999.0, 1.0);
test(0.0,0.0,1.0,0.0);
test(0.0,0.0,0.0,0.0);
test(0.0,0.0,-1.0,0.0);
test(0.0, 0.0, 1.0, 0.0);
test(0.0, 0.0, 0.0, 0.0);
test(0.0, 0.0, -1.0, 0.0);
}
#[test]
fn test_absolute_value() {
let test = |start,end,value,expected| {
let bounds = Bounds::new(start,end);
let normalised = absolute_value(&(bounds,value));
assert_float_eq!(normalised,expected,ulps<=7)
let test = |start, end, value, expected| {
let bounds = Bounds::new(start, end);
let normalised = absolute_value(&(bounds, value));
assert_float_eq!(normalised, expected, ulps <= 7)
};
test(0.0,1.0,0.0,0.0);
test(0.0,1.0,0.1,0.1);
test(0.0,1.0,0.2,0.2);
test(0.0,1.0,0.3,0.3);
test(0.0,1.0,0.4,0.4);
test(0.0,1.0,0.5,0.5);
test(0.0,1.0,0.6,0.6);
test(0.0,1.0,0.7,0.7);
test(0.0,1.0,0.8,0.8);
test(0.0,1.0,0.9,0.9);
test(0.0,1.0,1.0,1.0);
test(0.0, 1.0, 0.0, 0.0);
test(0.0, 1.0, 0.1, 0.1);
test(0.0, 1.0, 0.2, 0.2);
test(0.0, 1.0, 0.3, 0.3);
test(0.0, 1.0, 0.4, 0.4);
test(0.0, 1.0, 0.5, 0.5);
test(0.0, 1.0, 0.6, 0.6);
test(0.0, 1.0, 0.7, 0.7);
test(0.0, 1.0, 0.8, 0.8);
test(0.0, 1.0, 0.9, 0.9);
test(0.0, 1.0, 1.0, 1.0);
test(0.0,1.0,-2.0,-2.0);
test(0.0,1.0,-1.0,-1.0);
test(0.0,1.0,2.0,2.0);
test(0.0,1.0,3.0,3.0);
test(0.0, 1.0, -2.0, -2.0);
test(0.0, 1.0, -1.0, -1.0);
test(0.0, 1.0, 2.0, 2.0);
test(0.0, 1.0, 3.0, 3.0);
test(-1.0,1.0,0.0,-1.0);
test(-1.0,1.0,0.25,-0.5);
test(-1.0,1.0,0.5,0.0);
test(-1.0,1.0,0.75,0.5);
test(-1.0,1.0,1.0,1.0);
test(-1.0, 1.0, 0.0, -1.0);
test(-1.0, 1.0, 0.25, -0.5);
test(-1.0, 1.0, 0.5, 0.0);
test(-1.0, 1.0, 0.75, 0.5);
test(-1.0, 1.0, 1.0, 1.0);
test(1.0,-1.0,1.0,-1.0);
test(1.0,-1.0,0.75,-0.5);
test(1.0,-1.0,0.5,0.0);
test(1.0,-1.0,0.25,0.5);
test(1.0,-1.0,0.0,1.0);
test(1.0, -1.0, 1.0, -1.0);
test(1.0, -1.0, 0.75, -0.5);
test(1.0, -1.0, 0.5, 0.0);
test(1.0, -1.0, 0.25, 0.5);
test(1.0, -1.0, 0.0, 1.0);
test(-10.0,20.0,0.0,-10.0);
test(-10.0,20.0,1.0,20.0);
test(-10.0,20.0,0.33333333,0.0);
test(-10.0, 20.0, 0.0, -10.0);
test(-10.0, 20.0, 1.0, 20.0);
test(-10.0, 20.0, 0.33333333, 0.0);
test(-999999999.0,999999999.0,0.0,-999999999.0);
test(-999999999.0,999999999.0,0.5,0.0);
test(-999999999.0,999999999.0,1.0,999999999.0);
test(-999999999.0, 999999999.0, 0.0, -999999999.0);
test(-999999999.0, 999999999.0, 0.5, 0.0);
test(-999999999.0, 999999999.0, 1.0, 999999999.0);
test(0.0,0.0,1.0,0.0);
test(1.0,1.0,1.0,1.0);
test(1.0,1.0,2.0,1.0);
test(1.0,1.0,-2.0,1.0);
test(0.0, 0.0, 1.0, 0.0);
test(1.0, 1.0, 1.0, 1.0);
test(1.0, 1.0, 2.0, 1.0);
test(1.0, 1.0, -2.0, 1.0);
}
#[test]
fn test_position_to_normalised_value() {
let test = |pos,width,expected| {
let result = position_to_normalised_value(&pos,&width);
assert_float_eq!(result,expected,ulps<=7)
let test = |pos, width, expected| {
let result = position_to_normalised_value(&pos, &width);
assert_float_eq!(result, expected, ulps <= 7)
};
for &y in &[-100.0, 0.0, 100.0, NAN] {
test(Vector2::new(50.0,y),100.0,1.0);
test(Vector2::new(0.0,y),100.0,0.5);
test(Vector2::new(-50.0,y),100.0,0.0);
test(Vector2::new(50.0, y), 100.0, 1.0);
test(Vector2::new(0.0, y), 100.0, 0.5);
test(Vector2::new(-50.0, y), 100.0, 0.0);
test(Vector2::new(100.0,y),100.0,1.5);
test(Vector2::new(-100.0,y),100.0,-0.5);
test(Vector2::new(150.0,y),100.0,2.0);
test(Vector2::new(-150.0,y),100.0,-1.0);
test(Vector2::new(200.0,y),100.0,2.5);
test(Vector2::new(-200.0,y),100.0,-1.5);
test(Vector2::new(100.0, y), 100.0, 1.5);
test(Vector2::new(-100.0, y), 100.0, -0.5);
test(Vector2::new(150.0, y), 100.0, 2.0);
test(Vector2::new(-150.0, y), 100.0, -1.0);
test(Vector2::new(200.0, y), 100.0, 2.5);
test(Vector2::new(-200.0, y), 100.0, -1.5);
test(Vector2::new(-200.0,y),0.0,0.0);
test(Vector2::new(-200.0, y), 0.0, 0.0);
}
}
#[test]
fn test_value_in_bounds() {
let test = |start,end,value,expected| {
let result = value_in_bounds(value,Bounds::new(start,end));
assert_eq!(result,expected, "Testing whether {} in ]{},{}[", value,start,end)
let test = |start, end, value, expected| {
let result = value_in_bounds(value, Bounds::new(start, end));
assert_eq!(result, expected, "Testing whether {} in ]{},{}[", value, start, end)
};
test(0.0,1.0,0.0,true);
test(0.0,1.0,0.5,true);
test(0.0,1.0,1.0,true);
test(0.0,1.0,1.00001,false);
test(0.0,1.0,-0.00001,false);
test(0.0, 1.0, 0.0, true);
test(0.0, 1.0, 0.5, true);
test(0.0, 1.0, 1.0, true);
test(0.0, 1.0, 1.00001, false);
test(0.0, 1.0, -0.00001, false);
test(0.0,10.0,10.0,true);
test(0.0,10.0,9.999999,true);
test(0.0,10.0,11.0,false);
test(0.0, 10.0, 10.0, true);
test(0.0, 10.0, 9.999999, true);
test(0.0, 10.0, 11.0, false);
test(-100.0,10.0,11.0,false);
test(-101.0,10.0,-100.0,true);
test(-101.0,10.0,-101.0,true);
test(-101.0,10.0,-101.1,false);
test(-100.0, 10.0, 11.0, false);
test(-101.0, 10.0, -100.0, true);
test(-101.0, 10.0, -101.0, true);
test(-101.0, 10.0, -101.1, false);
test(0.0,0.0,0.0,true);
test(0.0,0.0,1.0,false);
test(0.0,0.0,-1.0,false);
test(0.0, 0.0, 0.0, true);
test(0.0, 0.0, 1.0, false);
test(0.0, 0.0, -1.0, false);
}
#[test]
fn test_bounds_in_bounds() {
let test = |start1,end1,start2,end2,expected| {
let result = bounds_in_bounds(Bounds::new(start1,start2),Bounds::new(start2,end2));
assert_eq!(result,expected,
"Testing whether ]{},{}[ in ]{},{}[", start1,end1,start2,end2);
let test = |start1, end1, start2, end2, expected| {
let result = bounds_in_bounds(Bounds::new(start1, start2), Bounds::new(start2, end2));
assert_eq!(
result, expected,
"Testing whether ]{},{}[ in ]{},{}[",
start1, end1, start2, end2
);
};
test(0.0,1.0,0.0,1.0,true);
test(0.0,1.0,1.0,2.0,false);
test(0.0,1.0,0.5,2.0,false);
test(0.0,1.0,-100.0,100.0,true);
test(0.0,1.0,-100.0,-99.0,false);
test(0.0,1.0,0.1,0.9,false);
test(-100.0,200.0,50.0,75.0,false);
test(-100.0,200.0,-50.0,75.0,false);
test(-100.0,200.0,-50.0,-75.0,false);
test(-100.0,200.0,-50.0,99999.0,false);
test(-100.0,200.0,-99999.0,0.0,true);
test(-100.0,200.0,-99999.0,99999.0,true);
test(0.0, 1.0, 0.0, 1.0, true);
test(0.0, 1.0, 1.0, 2.0, false);
test(0.0, 1.0, 0.5, 2.0, false);
test(0.0, 1.0, -100.0, 100.0, true);
test(0.0, 1.0, -100.0, -99.0, false);
test(0.0, 1.0, 0.1, 0.9, false);
test(-100.0, 200.0, 50.0, 75.0, false);
test(-100.0, 200.0, -50.0, 75.0, false);
test(-100.0, 200.0, -50.0, -75.0, false);
test(-100.0, 200.0, -50.0, 99999.0, false);
test(-100.0, 200.0, -99999.0, 0.0, true);
test(-100.0, 200.0, -99999.0, 99999.0, true);
test(0.0,0.0,0.0,0.0,true);
test(0.0,0.0,-1.0,2.0,true);
test(0.0,0.0,1.0,2.0,false);
test(0.0, 0.0, 0.0, 0.0, true);
test(0.0, 0.0, -1.0, 2.0, true);
test(0.0, 0.0, 1.0, 2.0, false);
}
#[test]
fn test_clamp_with_overflow() {
let test = |value,bounds,expected| {
let result = clamp_with_overflow(&value,&bounds);
assert_float_eq!(result,expected,ulps<=7)
let test = |value, bounds, expected| {
let result = clamp_with_overflow(&value, &bounds);
assert_float_eq!(result, expected, ulps <= 7)
};
test(0.0,Some(Bounds::new(0.0,1.0)), 0.0);
test(-1.0,Some(Bounds::new(0.0,1.0)), 0.0);
test(2.0,Some(Bounds::new(0.0,1.0)), 1.0);
test(0.0, Some(Bounds::new(0.0, 1.0)), 0.0);
test(-1.0, Some(Bounds::new(0.0, 1.0)), 0.0);
test(2.0, Some(Bounds::new(0.0, 1.0)), 1.0);
test(-1.0,None, 0.0);
test(2.0,None,1.0);
test(-1.0, None, 0.0);
test(2.0, None, 1.0);
test(-999.0,Some(Bounds::new(-1.0,100.0)), -1.0);
test(999.0,Some(Bounds::new(-1.0,100.0)), 100.0);
test(-1.0,Some(Bounds::new(-1.0,100.0)), -1.0);
test(0.0,Some(Bounds::new(-1.0,100.0)), 0.0);
test(99.0,Some(Bounds::new(-1.0,100.0)), 99.0);
test(100.0,Some(Bounds::new(-1.0,100.0)), 100.0);
test(100.01,Some(Bounds::new(-1.0,100.0)), 100.0);
test(-999.0, Some(Bounds::new(-1.0, 100.0)), -1.0);
test(999.0, Some(Bounds::new(-1.0, 100.0)), 100.0);
test(-1.0, Some(Bounds::new(-1.0, 100.0)), -1.0);
test(0.0, Some(Bounds::new(-1.0, 100.0)), 0.0);
test(99.0, Some(Bounds::new(-1.0, 100.0)), 99.0);
test(100.0, Some(Bounds::new(-1.0, 100.0)), 100.0);
test(100.01, Some(Bounds::new(-1.0, 100.0)), 100.0);
}
#[test]
fn test_should_clamp_with_overflow() {
let test = |inner,outer,expected| {
let result = should_clamp_with_overflow(&inner,&outer);
assert_eq!(result,expected);
let test = |inner, outer, expected| {
let result = should_clamp_with_overflow(&inner, &outer);
assert_eq!(result, expected);
};
test(Bounds::new(0.0,1.0),Some(Bounds::new(0.0,1.0)),true);
test(Bounds::new(0.0,1.0),Some(Bounds::new(1.0,2.0)),false);
test(Bounds::new(0.0,1.0),Some(Bounds::new(0.5,2.0)),false);
test(Bounds::new(0.0,1.0),Some(Bounds::new(-100.0,100.0)),true);
test(Bounds::new(0.0,1.0),Some(Bounds::new(-100.0,-99.0)),false);
test(Bounds::new(0.0,1.0),Some(Bounds::new(0.1,0.9)),false);
test(Bounds::new(-100.0,200.0),Some(Bounds::new(50.0,75.0)),false);
test(Bounds::new(-100.0,200.0),Some(Bounds::new(-50.0,75.0)),false);
test(Bounds::new(-100.0,200.0),Some(Bounds::new(-50.0,-75.0)),false);
test(Bounds::new(-100.0,200.0),Some(Bounds::new(-50.0,99999.0)),false);
test(Bounds::new(-100.0,200.0),Some(Bounds::new(-99999.0,0.0)),false);
test(Bounds::new(-100.0,200.0),Some(Bounds::new(-99999.0,99999.0)),true);
test(Bounds::new(-100.0,0.0),None,false);
test(Bounds::new(0.1,1.1),None,false);
test(Bounds::new(-9.1,2.1),None,false);
test(Bounds::new(0.25,0.7),None,true);
test(Bounds::new(0.0, 1.0), Some(Bounds::new(0.0, 1.0)), true);
test(Bounds::new(0.0, 1.0), Some(Bounds::new(1.0, 2.0)), false);
test(Bounds::new(0.0, 1.0), Some(Bounds::new(0.5, 2.0)), false);
test(Bounds::new(0.0, 1.0), Some(Bounds::new(-100.0, 100.0)), true);
test(Bounds::new(0.0, 1.0), Some(Bounds::new(-100.0, -99.0)), false);
test(Bounds::new(0.0, 1.0), Some(Bounds::new(0.1, 0.9)), false);
test(Bounds::new(-100.0, 200.0), Some(Bounds::new(50.0, 75.0)), false);
test(Bounds::new(-100.0, 200.0), Some(Bounds::new(-50.0, 75.0)), false);
test(Bounds::new(-100.0, 200.0), Some(Bounds::new(-50.0, -75.0)), false);
test(Bounds::new(-100.0, 200.0), Some(Bounds::new(-50.0, 99999.0)), false);
test(Bounds::new(-100.0, 200.0), Some(Bounds::new(-99999.0, 0.0)), false);
test(Bounds::new(-100.0, 200.0), Some(Bounds::new(-99999.0, 99999.0)), true);
test(Bounds::new(-100.0, 0.0), None, false);
test(Bounds::new(0.1, 1.1), None, false);
test(Bounds::new(-9.1, 2.1), None, false);
test(Bounds::new(0.25, 0.7), None, true);
test(Bounds::new(0.0,0.0),None,true);
test(Bounds::new(0.0, 0.0), None, true);
}
}

View File

@ -7,11 +7,11 @@ use crate::prelude::*;
use crate::component;
use enso_frp as frp;
use ensogl_core::application::Application;
use ensogl_core::application;
use ensogl_core::application::Application;
use ensogl_core::display;
use ensogl_core::display::object::ObjectOps;
use ensogl_core::display::shape::StyleWatchFrp;
use ensogl_core::display;
use ensogl_text as text;
@ -20,7 +20,7 @@ use ensogl_text as text;
// === Constants ===
// =================
const LABEL_OFFSET : f32 = 13.0;
const LABEL_OFFSET: f32 = 13.0;
@ -41,23 +41,23 @@ ensogl_core::define_endpoints! {
// === Model ===
// ==============
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
pub struct Model {
/// Root object. Required as the rendered text label will have an offset relative to the
/// base position of the root, depending on the position of the decimal separator.
root : display::object::Instance,
root: display::object::Instance,
/// Label containing the text to display. This is the label that will be shown.
label_full : text::Area,
label_full: text::Area,
/// This label contains the text to the left of the decimal. This is here, so we can get
/// information about the text width of this portion of the label. This label will
/// not appear in the UI.
label_left : text::Area,
label_left: text::Area,
}
impl component::Model for Model {
fn new(app:&Application) -> Self {
let logger = Logger::new("DecimalAlignedLabel");
let root = display::object::Instance::new(&logger);
fn new(app: &Application) -> Self {
let logger = Logger::new("DecimalAlignedLabel");
let root = display::object::Instance::new(&logger);
let label_full = app.new_view::<text::Area>();
let label_left = app.new_view::<text::Area>();
@ -67,13 +67,13 @@ impl component::Model for Model {
root.add_child(&label_full);
root.add_child(&label_left);
Self{root,label_full,label_left}
Self { root, label_full, label_left }
}
}
impl component::Frp<Model> for Frp {
fn init(&self, _app:&Application, model:&Model, _style:&StyleWatchFrp) {
let frp = &self;
fn init(&self, _app: &Application, model: &Model, _style: &StyleWatchFrp) {
let frp = &self;
let network = &frp.network;
frp::extend! { network
@ -94,14 +94,22 @@ impl component::Frp<Model> for Frp {
}
impl display::Object for Model {
fn display_object(&self) -> &display::object::Instance { self.root.display_object() }
fn display_object(&self) -> &display::object::Instance {
self.root.display_object()
}
}
/// Decimal aligned text label that shows the text representation of a floating point number.
pub type FloatLabel = component::Component<Model,Frp>;
pub type FloatLabel = component::Component<Model, Frp>;
impl application::View for FloatLabel {
fn label() -> &'static str { "FloatLabel" }
fn new(app:&Application) -> Self { FloatLabel::new(app) }
fn app(&self) -> &Application { &self.app }
fn label() -> &'static str {
"FloatLabel"
}
fn new(app: &Application) -> Self {
FloatLabel::new(app)
}
fn app(&self) -> &Application {
&self.app
}
}

View File

@ -12,8 +12,8 @@ use ensogl_theme as theme;
use crate::shadow;
use super::model::Model;
use super::shape::shape_is_dragged;
use super::shape::relative_shape_down_position;
use super::shape::shape_is_dragged;
@ -23,7 +23,7 @@ use super::shape::relative_shape_down_position;
/// Compute the slider width from the given shape size. For use in FRP, thus taking a reference.
#[allow(clippy::trivially_copy_pass_by_ref)]
fn slider_area_width(size:&Vector2) -> f32 {
fn slider_area_width(size: &Vector2) -> f32 {
// Radius of the rounded corners of the background shape.
let rounded_width = size.y / 2.0;
size.x - rounded_width
@ -39,64 +39,66 @@ fn slider_area_width(size:&Vector2) -> f32 {
/// of the `common::Model`.
pub struct Frp {
/// Current maximum extent of the track in scene coordinate space.
pub track_max_width : frp::Stream<f32>,
pub track_max_width: frp::Stream<f32>,
/// Indicates whether there is an ongoing dragging action from the left overflow shape.
pub is_dragging_left_overflow : frp::Stream<bool>,
pub is_dragging_left_overflow: frp::Stream<bool>,
/// Indicates whether there is an ongoing dragging action from the right overflow shape.
pub is_dragging_right_overflow : frp::Stream<bool>,
pub is_dragging_right_overflow: frp::Stream<bool>,
/// Indicates whether there is an ongoing dragging action from the track shape.
pub is_dragging_track : frp::Stream<bool>,
pub is_dragging_track: frp::Stream<bool>,
/// Indicates whether there is an ongoing dragging action from the background shape.
pub is_dragging_background : frp::Stream<bool>,
pub is_dragging_background: frp::Stream<bool>,
/// Indicates whether there is an ongoing dragging action from the invisible shape that covers
/// the left end of the track.
pub is_dragging_left_handle : frp::Stream<bool>,
pub is_dragging_left_handle: frp::Stream<bool>,
/// Indicates whether there is an ongoing dragging action from the invisible shape that covers
/// the right end of the track.
pub is_dragging_right_handle : frp::Stream<bool>,
pub is_dragging_right_handle: frp::Stream<bool>,
/// Indicates whether there is an ongoing dragging action on any of the component shapes.
pub is_dragging_any : frp::Stream<bool>,
pub is_dragging_any: frp::Stream<bool>,
/// Position of a click on the background. Position is given relative to the overall shape
/// origin and normalised to the shape size.
pub background_click : frp::Stream<Vector2>,
pub background_click: frp::Stream<Vector2>,
/// Position of a click on the track shape. Position is given relative to the overall shape
/// origin and normalised to the shape size.
pub track_click : frp::Stream<Vector2>,
pub track_click: frp::Stream<Vector2>,
/// Indicates whether the track is hovered..
pub track_hover : frp::Stream<bool>,
pub track_hover: frp::Stream<bool>,
}
impl Frp {
pub fn new
(model:&Model, style:&StyleWatchFrp, network:&Network, size:frp::Stream<Vector2>, mouse:&Mouse)
-> Frp {
let shadow = shadow::frp_from_style(style,theme::shadow);
pub fn new(
model: &Model,
style: &StyleWatchFrp,
network: &Network,
size: frp::Stream<Vector2>,
mouse: &Mouse,
) -> Frp {
let shadow = shadow::frp_from_style(style, theme::shadow);
let text_size = style.get_number(theme::text::size);
let is_dragging_left_overflow = shape_is_dragged(
network,&model.left_overflow.events,mouse);
let is_dragging_right_overflow = shape_is_dragged(
network,&model.right_overflow.events,mouse);
let is_dragging_track = shape_is_dragged(
network,&model.track.events, mouse);
let is_dragging_background = shape_is_dragged(
network,&model.background.events,mouse);
let is_dragging_left_handle = shape_is_dragged(
network,&model.track_handle_left.events,mouse);
let is_dragging_right_handle = shape_is_dragged(
network,&model.track_handle_right.events,mouse);
let is_dragging_left_overflow =
shape_is_dragged(network, &model.left_overflow.events, mouse);
let is_dragging_right_overflow =
shape_is_dragged(network, &model.right_overflow.events, mouse);
let is_dragging_track = shape_is_dragged(network, &model.track.events, mouse);
let is_dragging_background = shape_is_dragged(network, &model.background.events, mouse);
let is_dragging_left_handle =
shape_is_dragged(network, &model.track_handle_left.events, mouse);
let is_dragging_right_handle =
shape_is_dragged(network, &model.track_handle_right.events, mouse);
let background_click = relative_shape_down_position(
network,model.app.display.scene(),&model.background);
let track_click = relative_shape_down_position(
network,model.app.display.scene(),&model.track);
let background_click =
relative_shape_down_position(network, model.app.display.scene(), &model.background);
let track_click =
relative_shape_down_position(network, model.app.display.scene(), &model.track);
// Initialisation of components. Required for correct layout on startup.
model.label_right.set_position_y(text_size.value()/2.0);
model.label_left.set_position_y(text_size.value()/2.0);
model.label.set_position_y(text_size.value()/2.0);
model.caption_left.set_position_y(text_size.value()/2.0);
model.caption_center.set_position_y(text_size.value()/2.0);
model.label_right.set_position_y(text_size.value() / 2.0);
model.label_left.set_position_y(text_size.value() / 2.0);
model.label.set_position_y(text_size.value() / 2.0);
model.caption_left.set_position_y(text_size.value() / 2.0);
model.caption_center.set_position_y(text_size.value() / 2.0);
let bg_color = style.get_color(theme::component::slider::background);
model.set_background_color(bg_color.value());
@ -142,8 +144,18 @@ impl Frp {
init_shadow_padding.emit(());
Frp {track_max_width,is_dragging_left_overflow,is_dragging_right_overflow,
is_dragging_track,is_dragging_background,is_dragging_left_handle,
is_dragging_right_handle,is_dragging_any,background_click,track_click,track_hover}
Frp {
track_max_width,
is_dragging_left_overflow,
is_dragging_right_overflow,
is_dragging_track,
is_dragging_background,
is_dragging_left_handle,
is_dragging_right_handle,
is_dragging_any,
background_click,
track_click,
track_hover,
}
}
}

View File

@ -11,9 +11,9 @@ use ensogl_text as text;
use crate::component;
use super::Bounds;
use super::shape::*;
use super::decimal_aligned::FloatLabel;
use super::shape::*;
use super::Bounds;
@ -21,7 +21,7 @@ use super::decimal_aligned::FloatLabel;
// === Constants ===
// =================
const LABEL_OFFSET : f32 = 13.0;
const LABEL_OFFSET: f32 = 13.0;
@ -29,78 +29,78 @@ const LABEL_OFFSET : f32 = 13.0;
// === Model ===
// =============
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
pub struct Model {
/// Background shape that the other UI elements are placed on.
pub background : background::View,
pub background: background::View,
/// Visual element that indicates where in the available range the selected number or track is
/// located. Looks like a colored in bit of the background.
pub track : track::View,
pub track: track::View,
/// Invisible UI element that enables mouse interaction with the left end of the track. Will
/// always be placed on the left edge of the track.
pub track_handle_left : io_rect::View,
pub track_handle_left: io_rect::View,
/// Invisible UI element that enables mouse interaction with the right end of the track. Will
/// always be placed on the right edge of the track.
pub track_handle_right : io_rect::View,
pub track_handle_right: io_rect::View,
/// Icon that can be used out of range values. The left overflow is placed on the left side
/// of the shape and looks like an arrow/triangle pointing left.
pub left_overflow : left_overflow::View,
pub left_overflow: left_overflow::View,
/// Icon that can be used out of range values. The left overflow is placed on the right side
/// of the shape and looks like an arrow/triangle pointing right.
pub right_overflow : right_overflow::View,
pub right_overflow: right_overflow::View,
/// A label that is centered on the background and can be set to show a floating point value
/// that is centered on the decimal label.
pub label : FloatLabel,
pub label: FloatLabel,
/// A label that is aligned to the left edge of the background
pub label_left : text::Area,
pub label_left: text::Area,
/// A label that is aligned to the right edge of the background
pub label_right : text::Area,
pub label_right: text::Area,
/// A label that is left aligned on the background. Meant to contain a caption describing the
/// value that is selected. For example "Alpha", "Red", or "Size".
pub caption_left : text::Area,
pub caption_left: text::Area,
/// A label that is centered on the background. Meant to contain a caption describing the
/// range that is selected. For example "Allowed Size", or "Valid Price".
pub caption_center : text::Area,
pub caption_center: text::Area,
/// Shape root that all other elements are parented to. Should be used to place the shapes as
/// a group.
pub root : display::object::Instance,
pub root: display::object::Instance,
background_color : Rc<RefCell<color::Rgba>>,
track_color : Rc<RefCell<color::Rgba>>,
background_left_corner_roundness : Rc<Cell<bool>>,
background_right_corner_roundness : Rc<Cell<bool>>,
padding : Rc<Cell<f32>>,
background_color: Rc<RefCell<color::Rgba>>,
track_color: Rc<RefCell<color::Rgba>>,
background_left_corner_roundness: Rc<Cell<bool>>,
background_right_corner_roundness: Rc<Cell<bool>>,
padding: Rc<Cell<f32>>,
pub app : Application,
pub app: Application,
}
impl component::Model for Model {
fn new(app: &Application) -> Self {
let logger = Logger::new("selector::common::Model");
let root = display::object::Instance::new(&logger);
let label = app.new_view::<FloatLabel>();
let label_left = app.new_view::<text::Area>();
let label_right = app.new_view::<text::Area>();
let caption_center = app.new_view::<text::Area>();
let caption_left = app.new_view::<text::Area>();
let background = background::View::new(&logger);
let track = track::View::new(&logger);
let track_handle_left = io_rect::View::new(&logger);
let logger = Logger::new("selector::common::Model");
let root = display::object::Instance::new(&logger);
let label = app.new_view::<FloatLabel>();
let label_left = app.new_view::<text::Area>();
let label_right = app.new_view::<text::Area>();
let caption_center = app.new_view::<text::Area>();
let caption_left = app.new_view::<text::Area>();
let background = background::View::new(&logger);
let track = track::View::new(&logger);
let track_handle_left = io_rect::View::new(&logger);
let track_handle_right = io_rect::View::new(&logger);
let left_overflow = left_overflow::View::new(&logger);
let right_overflow = right_overflow::View::new(&logger);
let background_color = default();
let track_color = default();
let background_left_corner_roundness = default();
let left_overflow = left_overflow::View::new(&logger);
let right_overflow = right_overflow::View::new(&logger);
let background_color = default();
let track_color = default();
let background_left_corner_roundness = default();
let background_right_corner_roundness = default();
let padding = default();
let padding = default();
let app = app.clone_ref();
let app = app.clone_ref();
let scene = app.display.scene();
scene.layers.add_global_shapes_order_dependency::<background::View,track::View>();
scene.layers.add_global_shapes_order_dependency::<track::View,left_overflow::View>();
scene.layers.add_global_shapes_order_dependency::<track::View,right_overflow::View>();
scene.layers.add_global_shapes_order_dependency::<track::View,io_rect::View>();
scene.layers.add_global_shapes_order_dependency::<background::View, track::View>();
scene.layers.add_global_shapes_order_dependency::<track::View, left_overflow::View>();
scene.layers.add_global_shapes_order_dependency::<track::View, right_overflow::View>();
scene.layers.add_global_shapes_order_dependency::<track::View, io_rect::View>();
root.add_child(&label);
root.add_child(&label_left);
@ -120,36 +120,52 @@ impl component::Model for Model {
caption_center.remove_from_scene_layer(&scene.layers.main);
caption_center.add_to_scene_layer(&scene.layers.label);
Self{background,track,track_handle_left,track_handle_right,left_overflow,right_overflow,
label,label_left,label_right,caption_left,caption_center,root,background_color,
track_color,background_left_corner_roundness,background_right_corner_roundness,padding,
app}
Self {
background,
track,
track_handle_left,
track_handle_right,
left_overflow,
right_overflow,
label,
label_left,
label_right,
caption_left,
caption_center,
root,
background_color,
track_color,
background_left_corner_roundness,
background_right_corner_roundness,
padding,
app,
}
}
}
impl Model {
/// Set the size of the overall shape, taking into account the extra padding required to
/// render the shadow.
pub fn set_size(&self, size:Vector2, shadow_padding:Vector2) {
pub fn set_size(&self, size: Vector2, shadow_padding: Vector2) {
let size_with_shadow = size + shadow_padding;
self.background.size.set(size_with_shadow);
self.left_overflow.size.set(size_with_shadow);
self.right_overflow.size.set(size_with_shadow);
let padding = Vector2(self.padding.get()*2.0,self.padding.get()*2.0);
self.track.size.set(size_with_shadow-padding);
let padding = Vector2(self.padding.get() * 2.0, self.padding.get() * 2.0);
self.track.size.set(size_with_shadow - padding);
let left_padding = LABEL_OFFSET;
let left_padding = LABEL_OFFSET;
let overflow_icon_size = size.y;
let label_offset = size.x / 2.0 - overflow_icon_size + left_padding;
let label_offset = size.x / 2.0 - overflow_icon_size + left_padding;
self.label_left.set_position_x(-label_offset);
self.label_right.set_position_x(label_offset-self.label_right.width.value());
self.label_right.set_position_x(label_offset - self.label_right.width.value());
let overflow_icon_offset = size.x / 2.0 - overflow_icon_size / 2.0;
self.left_overflow.set_position_x(-overflow_icon_offset);
self.right_overflow.set_position_x(overflow_icon_offset);
let track_handle_size = Vector2::new(size.y/2.0,size.y);
let track_handle_size = Vector2::new(size.y / 2.0, size.y);
self.track_handle_left.size.set(track_handle_size);
self.track_handle_right.size.set(track_handle_size);
}
@ -157,10 +173,10 @@ impl Model {
/// Update the position of the captions based on the size of the shape and the text size. Takes
///arguments in the way they are provided in the FRP network (as reference to a tuple) to make
///usage more ergonomic on the call-site.
pub fn update_caption_position(&self, (size,text_size):&(Vector2,f32)) {
let left_padding = LABEL_OFFSET;
pub fn update_caption_position(&self, (size, text_size): &(Vector2, f32)) {
let left_padding = LABEL_OFFSET;
let overflow_icon_size = size.y / 2.0;
let caption_offset = size.x / 2.0 - overflow_icon_size - left_padding;
let caption_offset = size.x / 2.0 - overflow_icon_size - left_padding;
self.caption_left.set_position_x(-caption_offset);
self.caption_left.set_position_y(text_size / 2.0);
self.caption_center.set_position_y(text_size / 2.0);
@ -168,7 +184,7 @@ impl Model {
/// Set whether to allow interactions with the edges of the track shape. If this is set to
/// `false`, both `track_handle_left` and `track_handle_right` are removed from the component.
pub fn use_track_handles(&self, value:bool) {
pub fn use_track_handles(&self, value: bool) {
if value {
self.track.add_child(&self.track_handle_left);
self.track.add_child(&self.track_handle_right);
@ -180,12 +196,12 @@ impl Model {
/// Set the track to cover the area from zero up to the given value. The value indicates the
/// width of the background that should be covered in the range 0..1.
pub fn set_background_value(&self, value:f32) {
pub fn set_background_value(&self, value: f32) {
self.track.left.set(0.0);
self.track.right.set(value);
}
pub fn set_background_color(&self, color:color::Rgba) {
pub fn set_background_color(&self, color: color::Rgba) {
self.background_color.as_ref().replace(color);
self.background.color.set(color.into());
}
@ -194,40 +210,40 @@ impl Model {
/// indicates location aon the background in the range 0..1 (were 0 is the left edge and 1 is
/// the right edge). To do the proper layout of the track handles this method also needs to be
/// passed the size of the shape.
pub fn set_background_range(&self, value:Bounds, size:Vector2) {
pub fn set_background_range(&self, value: Bounds, size: Vector2) {
self.track.left.set(value.start);
self.track.right.set(value.end);
self.track_handle_left.set_position_x(value.start * size.x - size.x / 2.0);
self.track_handle_right.set_position_x(value.end * size.x - size.x / 2.0);
self.track_handle_right.set_position_x(value.end * size.x - size.x / 2.0);
}
/// Set the label in the center of the background to show the given numeric value.
pub fn set_center_label_content(&self, value:f32) {
pub fn set_center_label_content(&self, value: f32) {
self.label.frp.set_content.emit(value)
}
/// Set the label at the left edge of the background to show the given numeric value.
pub fn set_left_label_content(&self, value:f32) {
pub fn set_left_label_content(&self, value: f32) {
self.label_left.frp.set_content.emit(format!("{:.2}", value))
}
/// Set the label at the right edge of the background to show the given numeric value.
pub fn set_right_label_content(&self, value:f32) {
pub fn set_right_label_content(&self, value: f32) {
self.label_right.frp.set_content.emit(format!("{:.2}", value))
}
pub fn set_caption_left(&self, caption:Option<String>) {
pub fn set_caption_left(&self, caption: Option<String>) {
let caption = caption.unwrap_or_default();
self.caption_left.frp.set_content.emit(caption);
}
pub fn set_caption_center(&self, caption:Option<String>) {
pub fn set_caption_center(&self, caption: Option<String>) {
let caption = caption.unwrap_or_default();
self.caption_center.frp.set_content.emit(caption);
}
pub fn show_left_overflow(&self, value:bool) {
pub fn show_left_overflow(&self, value: bool) {
if value {
self.root.add_child(&self.left_overflow);
} else {
@ -235,7 +251,7 @@ impl Model {
}
}
pub fn show_right_overflow(&self, value:bool) {
pub fn show_right_overflow(&self, value: bool) {
if value {
self.root.add_child(&self.right_overflow);
} else {
@ -243,39 +259,41 @@ impl Model {
}
}
pub fn left_corner_round(&self,value:bool) {
pub fn left_corner_round(&self, value: bool) {
let corner_roundness = if value { 1.0 } else { 0.0 };
self.background.corner_left.set(corner_roundness);
self.track.corner_left.set(corner_roundness);
self.background_left_corner_roundness.set(value);
}
pub fn right_corner_round(&self,value:bool) {
pub fn right_corner_round(&self, value: bool) {
let corner_roundness = if value { 1.0 } else { 0.0 };
self.background.corner_right.set(corner_roundness);
self.track.corner_right.set(corner_roundness);
self.background_right_corner_roundness.set(value);
}
pub fn set_track_color(&self, color:color::Rgba) {
pub fn set_track_color(&self, color: color::Rgba) {
self.track.track_color.set(color.into());
}
pub fn set_track_corner_round(&self, value:bool) {
pub fn set_track_corner_round(&self, value: bool) {
let corner_roundness = if value { 1.0 } else { 0.0 };
self.track.corner_inner.set(corner_roundness)
}
pub fn set_padding(&self, padding:f32) {
pub fn set_padding(&self, padding: f32) {
self.padding.set(padding);
}
pub fn show_background(&self, value:bool) {
pub fn show_background(&self, value: bool) {
if value {
self.background.show_shadow.set(1.0);
self.background.color.set(self.background_color.as_ref().clone().into_inner().into());
let left_corner_roundness = if self.background_left_corner_roundness.get() { 1.0 } else { 0.0 };
let right_corner_roundness = if self.background_right_corner_roundness.get() { 1.0 } else { 0.0 };
let left_corner_roundness =
if self.background_left_corner_roundness.get() { 1.0 } else { 0.0 };
let right_corner_roundness =
if self.background_right_corner_roundness.get() { 1.0 } else { 0.0 };
self.track.corner_right.set(right_corner_roundness);
self.track.corner_left.set(left_corner_roundness);
} else {
@ -288,5 +306,7 @@ impl Model {
}
impl display::Object for Model {
fn display_object(&self) -> &display::object::Instance { &self.root }
fn display_object(&self) -> &display::object::Instance {
&self.root
}
}

View File

@ -2,21 +2,21 @@
use crate::prelude::*;
use enso_frp as frp;
use ensogl_core::data::color;
use ensogl_core::application::Application;
use ensogl_core::display::shape::*;
use ensogl_core::data::color;
use ensogl_core::display::shape::StyleWatchFrp;
use ensogl_core::display::shape::*;
use ensogl_theme as theme;
use crate::component;
use crate::selector::shape::*;
use super::Bounds;
use super::bounds::absolute_value;
use super::bounds::clamp_with_overflow;
use super::bounds::normalise_value;
use super::bounds::position_to_normalised_value;
use super::shape::relative_shape_down_position;
use super::model::Model;
use super::shape::relative_shape_down_position;
use super::Bounds;
use crate::component;
use crate::selector::shape::*;
@ -43,23 +43,23 @@ ensogl_core::define_endpoints! {
}
impl component::Frp<Model> for Frp {
fn init(&self, app:&Application, model:&Model, style:&StyleWatchFrp){
let frp = &self;
fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) {
let frp = &self;
let network = &frp.network;
let scene = app.display.scene();
let mouse = &scene.mouse.frp;
let scene = app.display.scene();
let mouse = &scene.mouse.frp;
model.show_background(true);
let base_frp = super::Frp::new(model,style,network,frp.resize.clone().into(),mouse);
let base_frp = super::Frp::new(model, style, network, frp.resize.clone().into(), mouse);
let track_shape_system = scene.shapes.shape_system(PhantomData::<track::Shape>);
track_shape_system.shape_system.set_pointer_events(false);
let background_click = relative_shape_down_position(
network,model.app.display.scene(),&model.background);
let track_click = relative_shape_down_position(
network,model.app.display.scene(),&model.track);
let background_click =
relative_shape_down_position(network, model.app.display.scene(), &model.background);
let track_click =
relative_shape_down_position(network, model.app.display.scene(), &model.track);
let style_track_color = style.get_color(theme::component::slider::track::color);
@ -123,7 +123,7 @@ impl component::Frp<Model> for Frp {
}
// Init defaults.
frp.set_bounds(Bounds::new(0.0,1.0));
frp.set_bounds(Bounds::new(0.0, 1.0));
frp.allow_click_selection(false);
frp.use_overflow_bounds(None);
frp.set_value(0.5);

View File

@ -2,19 +2,19 @@
use crate::prelude::*;
use enso_frp as frp;
use ensogl_core::data::color;
use ensogl_core::application::Application;
use ensogl_core::display::shape::*;
use ensogl_core::data::color;
use ensogl_core::display::shape::StyleWatchFrp;
use ensogl_core::display::shape::*;
use ensogl_theme as theme;
use crate::component;
use super::Model;
use super::Bounds;
use super::bounds::absolute_value;
use super::bounds::normalise_value;
use super::bounds::should_clamp_with_overflow;
use super::Bounds;
use super::Model;
@ -40,11 +40,11 @@ ensogl_core::define_endpoints! {
}
impl component::Frp<Model> for Frp {
fn init(&self, app:&Application, model:&Model, style:&StyleWatchFrp){
let frp = &self;
let network = &frp.network;
let scene = app.display.scene();
let mouse = &scene.mouse.frp;
fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) {
let frp = &self;
let network = &frp.network;
let scene = app.display.scene();
let mouse = &scene.mouse.frp;
let base_frp = super::Frp::new(model, style, network, frp.resize.clone().into(), mouse);
@ -125,12 +125,11 @@ impl component::Frp<Model> for Frp {
}
// Init defaults
frp.set_bounds(Bounds::new(0.0,1.0));
frp.set_bounds(Bounds::new(0.0, 1.0));
frp.use_overflow_bounds(None);
frp.set_value(Bounds::new(0.25,0.75));
frp.set_value(Bounds::new(0.25, 0.75));
frp.set_left_corner_round(true);
frp.set_right_corner_round(true);
frp.set_track_color(style_track_color.value());
}
}

View File

@ -17,32 +17,32 @@ use crate::shadow;
/// meta information about it. This information can be used to align other shapes with the
/// background.
struct Background {
pub width : Var<Pixels>,
pub height : Var<Pixels>,
pub width: Var<Pixels>,
pub height: Var<Pixels>,
#[allow(dead_code)]
// This field is not used but should stay as part of the API for future use.
pub corner_radius : Var<Pixels>,
pub shape : AnyShape,
pub corner_radius: Var<Pixels>,
pub shape: AnyShape,
}
impl Background {
fn new(corner_left:&Var<f32>, corner_right:&Var<f32>, style:&StyleWatch)
-> Background {
let sprite_width : Var<Pixels> = "input_size.x".into();
let sprite_height : Var<Pixels> = "input_size.y".into();
fn new(corner_left: &Var<f32>, corner_right: &Var<f32>, style: &StyleWatch) -> Background {
let sprite_width: Var<Pixels> = "input_size.x".into();
let sprite_height: Var<Pixels> = "input_size.y".into();
let width = &sprite_width - shadow::size(style).px();
let height = &sprite_height - shadow::size(style).px();
let corner_radius = &height/2.0;
let rect_left = Rect((&width/2.0,&height)).corners_radius(&corner_radius*corner_left);
let rect_left = rect_left.translate_x(-&width/4.0);
let rect_right = Rect((&width/2.0,&height)).corners_radius(&corner_radius*corner_right);
let rect_right = rect_right.translate_x(&width/4.0);
let rect_center = Rect((&corner_radius*2.0,&height));
let width = &sprite_width - shadow::size(style).px();
let height = &sprite_height - shadow::size(style).px();
let corner_radius = &height / 2.0;
let rect_left = Rect((&width / 2.0, &height)).corners_radius(&corner_radius * corner_left);
let rect_left = rect_left.translate_x(-&width / 4.0);
let rect_right =
Rect((&width / 2.0, &height)).corners_radius(&corner_radius * corner_right);
let rect_right = rect_right.translate_x(&width / 4.0);
let rect_center = Rect((&corner_radius * 2.0, &height));
let shape = (rect_left+rect_right+rect_center).into();
let shape = (rect_left + rect_right + rect_center).into();
Background{width,height,corner_radius,shape}
Background { width, height, corner_radius, shape }
}
}
@ -133,29 +133,29 @@ pub mod track {
struct OverflowShape {
#[allow(dead_code)]
// This field is not used but should stay as part of the API for future use.
pub width : Var<Pixels>,
pub width: Var<Pixels>,
#[allow(dead_code)]
// This field is not used but should stay as part of the API for future use.
pub height : Var<Pixels>,
pub shape : AnyShape
pub height: Var<Pixels>,
pub shape: AnyShape,
}
impl OverflowShape {
fn new(style:&StyleWatch) -> Self {
let sprite_width : Var<Pixels> = "input_size.x".into();
let sprite_height : Var<Pixels> = "input_size.y".into();
fn new(style: &StyleWatch) -> Self {
let sprite_width: Var<Pixels> = "input_size.x".into();
let sprite_height: Var<Pixels> = "input_size.y".into();
let width = &sprite_width - shadow::size(style).px();
let height = &sprite_height - shadow::size(style).px();
let overflow_color = style.get_color(theme::component::slider::overflow::color);
let shape = Triangle(&sprite_height/6.0,&sprite_height/6.0);
let shape = shape.fill(&overflow_color);
let width = &sprite_width - shadow::size(style).px();
let height = &sprite_height - shadow::size(style).px();
let overflow_color = style.get_color(theme::component::slider::overflow::color);
let shape = Triangle(&sprite_height / 6.0, &sprite_height / 6.0);
let shape = shape.fill(&overflow_color);
let hover_area = Circle(&height);
let hover_area = hover_area.fill(HOVER_COLOR);
let shape = (shape + hover_area).into();
OverflowShape{width,height,shape}
OverflowShape { width, height, shape }
}
}
@ -207,8 +207,11 @@ use ensogl_core::display::Scene;
/// Return whether a dragging action has been started from the shape passed to this function. A
/// dragging action is started by a mouse down on the shape, followed by a movement of the mouse.
/// Dragging is ended by a mouse up.
pub fn shape_is_dragged
(network:&Network, shape:&ShapeViewEvents, mouse:&Mouse) -> enso_frp::Stream<bool> {
pub fn shape_is_dragged(
network: &Network,
shape: &ShapeViewEvents,
mouse: &Mouse,
) -> enso_frp::Stream<bool> {
enso_frp::extend! { network
mouse_up <- mouse.up.constant(());
mouse_down <- mouse.down.constant(());
@ -221,10 +224,10 @@ pub fn shape_is_dragged
/// Returns the position of a mouse down on a shape. The position is given in the shape's local
/// coordinate system
pub fn relative_shape_down_position<T:'static+display::Object+CloneRef>
( network : &Network
, scene : &Scene
, shape : &ShapeView<T>
pub fn relative_shape_down_position<T: 'static + display::Object + CloneRef>(
network: &Network,
scene: &Scene,
shape: &ShapeView<T>,
) -> enso_frp::Stream<Vector2> {
let mouse = &scene.mouse.frp;
enso_frp::extend! { network
@ -256,28 +259,28 @@ mod tests {
#[test]
fn test_shape_is_dragged() {
let network = enso_frp::Network::new("TestNetwork");
let mouse = enso_frp::io::Mouse::default();
let shape = ShapeViewEvents::default();
let mouse = enso_frp::io::Mouse::default();
let shape = ShapeViewEvents::default();
let is_dragged = shape_is_dragged(&network,&shape,&mouse);
let is_dragged = shape_is_dragged(&network, &shape, &mouse);
let _watch = is_dragged.register_watch();
// Default is false.
assert_eq!(is_dragged.value(),false);
assert_eq!(is_dragged.value(), false);
// Mouse down over shape activates dragging.
shape.mouse_over.emit(());
mouse.down.emit(Button::from_code(0));
assert_eq!(is_dragged.value(),true);
assert_eq!(is_dragged.value(), true);
// Release mouse stops dragging.
mouse.up.emit(Button::from_code(0));
assert_eq!(is_dragged.value(),false);
assert_eq!(is_dragged.value(), false);
// Mouse down while not over shape does not activate dragging.
shape.mouse_out.emit(());
mouse.down.emit(Button::from_code(0));
assert_eq!(is_dragged.value(),false);
assert_eq!(is_dragged.value(), false);
}
}

View File

@ -2,126 +2,137 @@
use crate::prelude::*;
use ensogl_core::data::color;
use ensogl_core::display::DomSymbol;
use ensogl_core::display::shape::*;
use ensogl_core::display::shape::AnyShape;
use ensogl_core::display::shape::*;
use ensogl_core::display::style;
use ensogl_core::display::DomSymbol;
use ensogl_core::frp;
use ensogl_core::system::web::StyleSetter;
use ensogl_theme as theme;
/// Defines the appearance of a shadow
#[derive(Debug,Copy,Clone)]
#[derive(Debug, Copy, Clone)]
pub struct Parameters {
base_color : color::Rgba,
fading : color::Rgba,
size : f32,
spread : f32,
exponent : f32,
offset_x : f32,
offset_y : f32
base_color: color::Rgba,
fading: color::Rgba,
size: f32,
spread: f32,
exponent: f32,
offset_x: f32,
offset_y: f32,
}
/// Loads shadow parameters from the given style, at the given path. The structure of the style
/// definition should be analogous to that at `ensogl_theme::shadow`.
pub fn parameters_from_style_path(style:&StyleWatch,path:impl Into<style::Path>) -> Parameters {
pub fn parameters_from_style_path(style: &StyleWatch, path: impl Into<style::Path>) -> Parameters {
let path: style::Path = path.into();
Parameters {
base_color: style.get_color(&path),
fading: style.get_color(&path.sub("fading")),
size: style.get_number(&path.sub("size")),
spread: style.get_number(&path.sub("spread")),
exponent: style.get_number(&path.sub("exponent")),
offset_x: style.get_number(&path.sub("offset_x")),
offset_y: style.get_number(&path.sub("offset_y"))
fading: style.get_color(&path.sub("fading")),
size: style.get_number(&path.sub("size")),
spread: style.get_number(&path.sub("spread")),
exponent: style.get_number(&path.sub("exponent")),
offset_x: style.get_number(&path.sub("offset_x")),
offset_y: style.get_number(&path.sub("offset_y")),
}
}
/// Utility method to retrieve the size of the shadow. can be used to determine shape padding etc.
pub fn size(style:&StyleWatch) -> f32 {
let parameters = parameters_from_style_path(style,theme::shadow);
pub fn size(style: &StyleWatch) -> f32 {
let parameters = parameters_from_style_path(style, theme::shadow);
parameters.size
}
/// Return a shadow for the given shape. Exact appearance will depend on the theme parameters.
pub fn from_shape(base_shape:AnyShape, style:&StyleWatch) -> AnyShape {
pub fn from_shape(base_shape: AnyShape, style: &StyleWatch) -> AnyShape {
let alpha = Var::<f32>::from(1.0);
from_shape_with_alpha(base_shape,&alpha,style)
from_shape_with_alpha(base_shape, &alpha, style)
}
/// Return a shadow for the given shape. Exact appearance will depend on the theme parameters.
/// The color will be multiplied with the given alpha value, which is useful for fade-in/out
/// animations.
pub fn from_shape_with_alpha(base_shape:AnyShape,alpha:&Var<f32>,style:&StyleWatch) -> AnyShape {
pub fn from_shape_with_alpha(
base_shape: AnyShape,
alpha: &Var<f32>,
style: &StyleWatch,
) -> AnyShape {
let parameters = parameters_from_style_path(style, theme::shadow);
from_shape_with_parameters_and_alpha(base_shape,parameters,alpha)
from_shape_with_parameters_and_alpha(base_shape, parameters, alpha)
}
/// Return a shadow for the given shape and shadow parameters.
pub fn from_shape_with_parameters(base_shape:AnyShape,parameters:Parameters) -> AnyShape {
pub fn from_shape_with_parameters(base_shape: AnyShape, parameters: Parameters) -> AnyShape {
let alpha = Var::<f32>::from(1.0);
from_shape_with_parameters_and_alpha(base_shape,parameters,&alpha)
from_shape_with_parameters_and_alpha(base_shape, parameters, &alpha)
}
/// Return a shadow for the given shape, shadow parameters and alpha. As in `from_shape_with_alpha`,
/// the shadow colors will be multiplied with the given alpha value.
pub fn from_shape_with_parameters_and_alpha
(base_shape:AnyShape,parameters:Parameters,alpha:&Var<f32>)
-> AnyShape {
let grow = Var::<f32>::from(parameters.size);
let shadow = base_shape.grow(grow);
let shadow = shadow.translate((parameters.offset_x.px(),parameters.offset_y.px()));
pub fn from_shape_with_parameters_and_alpha(
base_shape: AnyShape,
parameters: Parameters,
alpha: &Var<f32>,
) -> AnyShape {
let grow = Var::<f32>::from(parameters.size);
let shadow = base_shape.grow(grow);
let shadow = shadow.translate((parameters.offset_x.px(), parameters.offset_y.px()));
let base_color = Var::<color::Rgba>::from(parameters.base_color);
let base_color = base_color.multiply_alpha(alpha);
let base_color = Var::<color::Rgba>::from(parameters.base_color);
let base_color = base_color.multiply_alpha(alpha);
let fading_color = Var::<color::Rgba>::from(parameters.fading);
let fading_color = fading_color.multiply_alpha(alpha);
let fading_color = Var::<color::Rgba>::from(parameters.fading);
let fading_color = fading_color.multiply_alpha(alpha);
let shadow_color = color::gradient::Linear::<Var<color::LinearRgba>>
::new(fading_color.into_linear(),base_color.into_linear());
let shadow_color = shadow_color.sdf_sampler().size(parameters.size).spread(parameters.spread)
let shadow_color = color::gradient::Linear::<Var<color::LinearRgba>>::new(
fading_color.into_linear(),
base_color.into_linear(),
);
let shadow_color = shadow_color
.sdf_sampler()
.size(parameters.size)
.spread(parameters.spread)
.exponent(parameters.exponent);
let shadow = shadow.fill(shadow_color);
let shadow = shadow.fill(shadow_color);
shadow.into()
}
/// Add a theme defined box shadow to the given `DomSymbol`.
pub fn add_to_dom_element(element:&DomSymbol, style:&StyleWatch,logger:&Logger) {
let off_x = style.get_number(theme::shadow::offset_x);
let off_y = -style.get_number(theme::shadow::offset_y);
let alpha = style.get_number(ensogl_theme::shadow::html::alpha);
let blur = style.get_number(ensogl_theme::shadow::html::blur);
pub fn add_to_dom_element(element: &DomSymbol, style: &StyleWatch, logger: &Logger) {
let off_x = style.get_number(theme::shadow::offset_x);
let off_y = -style.get_number(theme::shadow::offset_y);
let alpha = style.get_number(ensogl_theme::shadow::html::alpha);
let blur = style.get_number(ensogl_theme::shadow::html::blur);
let spread = style.get_number(ensogl_theme::shadow::html::spread);
let shadow = format!("{}px {}px {}px {}px rgba(0,0,0,{})",off_x,off_y,blur,spread,alpha);
element.dom().set_style_or_warn("box-shadow",shadow,logger);
let shadow = format!("{}px {}px {}px {}px rgba(0,0,0,{})", off_x, off_y, blur, spread, alpha);
element.dom().set_style_or_warn("box-shadow", shadow, logger);
}
/// Provides FRP endpoints for the parameters that define the appearance of a shadow
#[derive(Debug,Clone,CloneRef)]
#[derive(Debug, Clone, CloneRef)]
#[allow(missing_docs)]
pub struct ParametersFrp {
pub base_color : frp::Sampler<color::Rgba>,
pub fading : frp::Sampler<color::Rgba>,
pub size : frp::Sampler<f32>,
pub spread : frp::Sampler<f32>,
pub exponent : frp::Sampler<f32>,
pub offset_x : frp::Sampler<f32>,
pub offset_y : frp::Sampler<f32>
pub base_color: frp::Sampler<color::Rgba>,
pub fading: frp::Sampler<color::Rgba>,
pub size: frp::Sampler<f32>,
pub spread: frp::Sampler<f32>,
pub exponent: frp::Sampler<f32>,
pub offset_x: frp::Sampler<f32>,
pub offset_y: frp::Sampler<f32>,
}
/// Return FRP endpoints for the parameters that define a shadow.
pub fn frp_from_style(style:&StyleWatchFrp,path:impl Into<style::Path>) -> ParametersFrp {
pub fn frp_from_style(style: &StyleWatchFrp, path: impl Into<style::Path>) -> ParametersFrp {
let path: style::Path = path.into();
ParametersFrp{
base_color : style.get_color(&path),
fading : style.get_color(&path.sub("fading")),
size : style.get_number(&path.sub("size")),
spread : style.get_number(&path.sub("spread")),
exponent : style.get_number(&path.sub("exponent")),
offset_x : style.get_number(&path.sub("offset_x")),
offset_y : style.get_number(&path.sub("offset_y"))
ParametersFrp {
base_color: style.get_color(&path),
fading: style.get_color(&path.sub("fading")),
size: style.get_number(&path.sub("size")),
spread: style.get_number(&path.sub("spread")),
exponent: style.get_number(&path.sub("exponent")),
offset_x: style.get_number(&path.sub("offset_x")),
offset_y: style.get_number(&path.sub("offset_y")),
}
}

View File

@ -13,15 +13,14 @@ use ensogl_core::display::shape::system::DynamicShapeInternals;
// =================
// === Colorable ===
// =================
/// A shape that can have a single color.
pub trait ColorableShape : DynamicShapeInternals {
pub trait ColorableShape: DynamicShapeInternals {
/// Set the color of the shape.
fn set_color(&self, color:color::Rgba);
fn set_color(&self, color: color::Rgba);
}
@ -54,17 +53,17 @@ ensogl_core::define_endpoints! {
// === Model ===
// =============
#[derive(Clone,CloneRef,Debug,Derivative)]
#[clone_ref(bound="Shape:CloneRef")]
#[derive(Clone, CloneRef, Debug, Derivative)]
#[clone_ref(bound = "Shape:CloneRef")]
struct Model<Shape> {
icon : ShapeView<Shape>,
icon: ShapeView<Shape>,
}
impl<Shape:ColorableShape+'static> Model<Shape> {
fn new(logger:impl AnyLogger) -> Self {
let logger = Logger::new_sub(logger,"ToggleButton");
let icon = ShapeView::new(&logger);
Self{icon}
impl<Shape: ColorableShape + 'static> Model<Shape> {
fn new(logger: impl AnyLogger) -> Self {
let logger = Logger::new_sub(logger, "ToggleButton");
let icon = ShapeView::new(&logger);
Self { icon }
}
}
@ -75,19 +74,19 @@ impl<Shape:ColorableShape+'static> Model<Shape> {
// ===================
/// A state a button can be in.
#[derive(Clone,Copy,Debug)]
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub struct ButtonState {
pub visible : bool,
pub toggled : bool,
pub hovered : bool,
pub pressed : bool,
pub visible: bool,
pub toggled: bool,
pub hovered: bool,
pub pressed: bool,
}
impl ButtonState {
/// Constructor.
pub fn new(visible:bool, toggled:bool, hovered:bool, pressed:bool) -> Self {
Self {visible,toggled,hovered,pressed}
pub fn new(visible: bool, toggled: bool, hovered: bool, pressed: bool) -> Self {
Self { visible, toggled, hovered, pressed }
}
}
@ -97,7 +96,7 @@ impl Default for ButtonState {
let toggled = false;
let hovered = false;
let pressed = false;
Self {visible,toggled,hovered,pressed}
Self { visible, toggled, hovered, pressed }
}
}
@ -108,31 +107,31 @@ impl Default for ButtonState {
// ===================
/// Button color scheme.
#[derive(Clone,Debug,Default)]
#[derive(Clone, Debug, Default)]
#[allow(missing_copy_implementations)]
#[allow(missing_docs)]
pub struct ColorScheme {
pub non_toggled : Option<color::Lcha>,
pub hovered : Option<color::Lcha>,
pub pressed : Option<color::Lcha>,
pub toggled : Option<color::Lcha>,
pub toggled_hovered : Option<color::Lcha>,
pub toggled_pressed : Option<color::Lcha>,
pub non_toggled: Option<color::Lcha>,
pub hovered: Option<color::Lcha>,
pub pressed: Option<color::Lcha>,
pub toggled: Option<color::Lcha>,
pub toggled_hovered: Option<color::Lcha>,
pub toggled_pressed: Option<color::Lcha>,
}
impl ColorScheme {
/// Query the scheme based on the button state.
pub fn query(&self, state:ButtonState) -> color::Lcha {
pub fn query(&self, state: ButtonState) -> color::Lcha {
match (state.visible, state.toggled, state.hovered, state.pressed) {
( false , _ , _ , _ ) => color::Lcha::transparent(),
( true , false, false, false ) => self.non_toggled(),
( true , false, false, true ) => self.pressed(),
( true , false, true , false ) => self.hovered(),
( true , false, true , true ) => self.pressed(),
( true , true , false, false ) => self.toggled(),
( true , true , false, true ) => self.toggled_pressed(),
( true , true , true , false ) => self.toggled_hovered(),
( true , true , true , true ) => self.toggled_pressed(),
(false, _, _, _) => color::Lcha::transparent(),
(true, false, false, false) => self.non_toggled(),
(true, false, false, true) => self.pressed(),
(true, false, true, false) => self.hovered(),
(true, false, true, true) => self.pressed(),
(true, true, false, false) => self.toggled(),
(true, true, false, true) => self.toggled_pressed(),
(true, true, true, false) => self.toggled_hovered(),
(true, true, true, true) => self.toggled_pressed(),
}
}
}
@ -142,28 +141,28 @@ impl ColorScheme {
#[allow(missing_docs)]
impl ColorScheme {
pub fn non_toggled (&self) -> color::Lcha {
pub fn non_toggled(&self) -> color::Lcha {
self.non_toggled.unwrap_or_else(color::Lcha::black)
}
pub fn hovered (&self) -> color::Lcha {
self.hovered.unwrap_or_else(||self.pressed())
pub fn hovered(&self) -> color::Lcha {
self.hovered.unwrap_or_else(|| self.pressed())
}
pub fn pressed (&self) -> color::Lcha {
self.hovered.unwrap_or_else(||self.toggled())
pub fn pressed(&self) -> color::Lcha {
self.hovered.unwrap_or_else(|| self.toggled())
}
pub fn toggled (&self) -> color::Lcha {
pub fn toggled(&self) -> color::Lcha {
self.toggled.unwrap_or_else(color::Lcha::black)
}
pub fn toggled_hovered (&self) -> color::Lcha {
self.toggled_hovered.unwrap_or_else(||self.toggled())
pub fn toggled_hovered(&self) -> color::Lcha {
self.toggled_hovered.unwrap_or_else(|| self.toggled())
}
pub fn toggled_pressed (&self) -> color::Lcha {
self.toggled_pressed.unwrap_or_else(||self.pressed())
pub fn toggled_pressed(&self) -> color::Lcha {
self.toggled_pressed.unwrap_or_else(|| self.pressed())
}
}
@ -175,12 +174,12 @@ impl ColorScheme {
/// A UI component that acts as a toggle which can be toggled on and of. Has a visible shape
/// that acts as button and changes color depending on the toggle state.
#[derive(CloneRef,Debug,Derivative)]
#[derivative(Clone(bound=""))]
#[derive(CloneRef, Debug, Derivative)]
#[derivative(Clone(bound = ""))]
#[allow(missing_docs)]
pub struct ToggleButton<Shape> {
pub frp : Frp,
model : Rc<Model<Shape>>,
pub frp: Frp,
model: Rc<Model<Shape>>,
}
impl<Shape> Deref for ToggleButton<Shape> {
@ -190,20 +189,20 @@ impl<Shape> Deref for ToggleButton<Shape> {
}
}
impl<Shape:ColorableShape+'static> ToggleButton<Shape>{
impl<Shape: ColorableShape + 'static> ToggleButton<Shape> {
/// Constructor.
pub fn new(logger:impl AnyLogger) -> Self {
let frp = Frp::new();
pub fn new(logger: impl AnyLogger) -> Self {
let frp = Frp::new();
let model = Rc::new(Model::<Shape>::new(logger));
Self {frp,model}.init_frp()
Self { frp, model }.init_frp()
}
fn init_frp(self) -> Self {
let network = &self.frp.network;
let frp = &self.frp;
let model = &self.model;
let color = color::Animation::new(network);
let icon = &model.icon.events;
let frp = &self.frp;
let model = &self.model;
let color = color::Animation::new(network);
let icon = &model.icon.events;
frp::extend! { network
@ -256,7 +255,7 @@ impl<Shape:ColorableShape+'static> ToggleButton<Shape>{
}
}
impl<T:display::Object> display::Object for ToggleButton<T> {
impl<T: display::Object> display::Object for ToggleButton<T> {
fn display_object(&self) -> &display::object::Instance {
self.model.icon.display_object()
}

View File

@ -16,13 +16,13 @@ pub use loops::*;
// === Utils ===
// =============
use std::ops::Mul;
use std::ops::Add;
use std::ops::Mul;
/// A generic trait constraint for interpolable types.
pub trait Interpolable<T:Copy> = Mul<f32, Output = T> + Add<T, Output = T> + Copy;
pub trait Interpolable<T: Copy> = Mul<f32, Output = T> + Add<T, Output = T> + Copy;
/// Linear interpolation function for any type implementing T * f32 and T + T.
pub fn linear_interpolation<T:Interpolable<T>>(a:T, b:T, t:f32) -> T {
pub fn linear_interpolation<T: Interpolable<T>>(a: T, b: T, t: f32) -> T {
a * (1.0 - t) + b * t
}

View File

@ -26,30 +26,32 @@ pub trait AnyFnEasing = 'static + Fn(f32) -> f32;
pub trait CloneableFnEasing = 'static + Clone + Fn(f32) -> f32;
macro_rules! define_in_out_easing_fn {
(fn $tname:ident $name:ident $lambda:expr) => { paste::item! {
/// A $name-in function type.
pub type [<$tname In>] = impl Clone + Fn(f32) -> f32;
/// A $name-out function type.
pub type [<$tname Out>] = impl Clone + Fn(f32) -> f32;
/// A $name-in-out function type.
pub type [<$tname InOut>] = impl Clone + Fn(f32) -> f32;
(fn $tname:ident $name:ident $lambda:expr) => {
paste::item! {
/// A $name-in function type.
pub type [<$tname In>] = impl Clone + Fn(f32) -> f32;
/// A $name-out function type.
pub type [<$tname Out>] = impl Clone + Fn(f32) -> f32;
/// A $name-in-out function type.
pub type [<$tname InOut>] = impl Clone + Fn(f32) -> f32;
/// A $name-in transition.
pub fn [<$name _in>]() -> [<$tname In>] { $lambda }
/// A $name-out transition.
pub fn [<$name _out>]() -> [<$tname Out>] { |t| { 1.0 - [<$name _in>]()(1.0 - t) } }
/// A $name-in-out transition.
pub fn [<$name _in_out>]() -> [<$tname InOut>] { |t| {
let t = t * 2.0;
if t < 1.0 { [<$name _in>]()(t) / 2.0 }
else { ([<$name _out>]()(t - 1.0) + 1.0) / 2.0 }
}}
/// A $name-in transition.
pub fn [<$name _in>]() -> [<$tname In>] { $lambda }
/// A $name-out transition.
pub fn [<$name _out>]() -> [<$tname Out>] { |t| { 1.0 - [<$name _in>]()(1.0 - t) } }
/// A $name-in-out transition.
pub fn [<$name _in_out>]() -> [<$tname InOut>] { |t| {
let t = t * 2.0;
if t < 1.0 { [<$name _in>]()(t) / 2.0 }
else { ([<$name _out>]()(t - 1.0) + 1.0) / 2.0 }
}}
// FIXME: Crashes the compiler. To be fixed one day.
// impl Default for [<$tname In>] { fn default() -> Self { [<$name _in>]() } }
// impl Default for [<$tname Out>] { fn default() -> Self { [<$name _out>]() } }
// impl Default for [<$tname InOut>] { fn default() -> Self { [<$name _in_out>]() } }
}};
// FIXME: Crashes the compiler. To be fixed one day.
// impl Default for [<$tname In>] { fn default() -> Self { [<$name _in>]() } }
// impl Default for [<$tname Out>] { fn default() -> Self { [<$name _out>]() } }
// impl Default for [<$tname InOut>] { fn default() -> Self { [<$name _in_out>]() } }
}
};
}
macro_rules! define_in_out_easing_fns {
@ -87,18 +89,22 @@ define_in_out_easing_fns! {
pub type Linear = impl Clone + Fn(f32) -> f32;
/// Linear transition.
pub fn linear() -> Linear { |t| { t } }
pub fn linear() -> Linear {
|t| t
}
/// A back-in transition with params.
pub fn back_in_params(t:f32, overshoot:f32) -> f32 { t * t * ((overshoot + 1.0) * t - overshoot) }
pub fn back_in_params(t: f32, overshoot: f32) -> f32 {
t * t * ((overshoot + 1.0) * t - overshoot)
}
/// A back-out transition with params.
pub fn back_out_params(t:f32, overshoot:f32) -> f32 {
pub fn back_out_params(t: f32, overshoot: f32) -> f32 {
1.0 - back_in_params(1.0 - t, overshoot)
}
/// A back-in-out transition with params.
pub fn back_in_out_params(t:f32, overshoot:f32) -> f32 {
pub fn back_in_out_params(t: f32, overshoot: f32) -> f32 {
let t = t * 2.0;
if t < 1.0 {
back_in_params(t, overshoot) / 2.0
@ -108,9 +114,9 @@ pub fn back_in_out_params(t:f32, overshoot:f32) -> f32 {
}
/// An elastic-in transition with params.
pub fn elastic_in_params(t:f32, period:f32, amplitude:f32) -> f32 {
pub fn elastic_in_params(t: f32, period: f32, amplitude: f32) -> f32 {
let mut amplitude = amplitude;
let overshoot = if amplitude <= 1.0 {
let overshoot = if amplitude <= 1.0 {
amplitude = 1.0;
period / 4.0
} else {
@ -121,12 +127,12 @@ pub fn elastic_in_params(t:f32, period:f32, amplitude:f32) -> f32 {
}
/// An elastic-out transition with params.
pub fn elastic_out_params(t:f32, period:f32, amplitude:f32) -> f32 {
pub fn elastic_out_params(t: f32, period: f32, amplitude: f32) -> f32 {
1.0 - elastic_in_params(1.0 - t, period, amplitude)
}
/// An elastic-in-out transition with params.
pub fn elastic_in_out_params(t:f32, period:f32, amplitude:f32) -> f32 {
pub fn elastic_in_out_params(t: f32, period: f32, amplitude: f32) -> f32 {
let t = t * 2.0;
if t < 1.0 {
elastic_in_params(t, period, amplitude) / 2.0
@ -142,69 +148,73 @@ pub fn elastic_in_out_params(t:f32, period:f32, amplitude:f32) -> f32 {
// ================
/// Easing animator value.
pub trait Value = Copy + Add<Self,Output=Self> + Mul<f32,Output=Self> + PartialEq + 'static;
pub trait Value = Copy + Add<Self, Output = Self> + Mul<f32, Output = Self> + PartialEq + 'static;
/// Easing animator callback.
pub trait Callback<T> = Fn1<T> + 'static;
/// Handy alias for `Simulator` with a boxed closure callback.
pub type DynAnimator<T,F> = Animator<T,F,Box<dyn Fn(f32)>,Box<dyn Fn(EndStatus)>>;
pub type DynAnimator<T, F> = Animator<T, F, Box<dyn Fn(f32)>, Box<dyn Fn(EndStatus)>>;
/// Easing animator. Allows animating any value which implements `Value` according to one of the
/// tween functions.
#[derive(CloneRef,Derivative)]
#[derivative(Clone(bound=""))]
pub struct Animator<T,F,OnStep=(),OnEnd=()> {
data : Rc<AnimatorData<T,F,OnStep,OnEnd>>,
animation_loop : AnimationLoop<T,F,OnStep,OnEnd>,
#[derive(CloneRef, Derivative)]
#[derivative(Clone(bound = ""))]
pub struct Animator<T, F, OnStep = (), OnEnd = ()> {
data: Rc<AnimatorData<T, F, OnStep, OnEnd>>,
animation_loop: AnimationLoop<T, F, OnStep, OnEnd>,
}
impl<T,F,OnStep,OnEnd> Deref for Animator<T,F,OnStep,OnEnd> {
type Target = Rc<AnimatorData<T,F,OnStep,OnEnd>>;
impl<T, F, OnStep, OnEnd> Deref for Animator<T, F, OnStep, OnEnd> {
type Target = Rc<AnimatorData<T, F, OnStep, OnEnd>>;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T,F,OnStep,OnEnd> Debug for Animator<T,F,OnStep,OnEnd> {
fn fmt(&self, f:&mut fmt::Formatter<'_>) -> fmt::Result {
write!(f,"Animator")
impl<T, F, OnStep, OnEnd> Debug for Animator<T, F, OnStep, OnEnd> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Animator")
}
}
/// Internal data of `Animator`.
#[derive(Derivative)]
#[derivative(Debug(bound="T:Debug+Copy"))]
#[derivative(Debug(bound = "T:Debug+Copy"))]
#[allow(missing_docs)]
pub struct AnimatorData<T,F,OnStep,OnEnd> {
pub duration : Cell<f32>,
pub start_value : Cell<T>,
pub target_value : Cell<T>,
pub value : Cell<T>,
pub active : Cell<bool>,
#[derivative(Debug="ignore")]
pub tween_fn : F,
#[derivative(Debug="ignore")]
pub callback : OnStep,
#[derivative(Debug="ignore")]
pub on_end : OnEnd,
pub struct AnimatorData<T, F, OnStep, OnEnd> {
pub duration: Cell<f32>,
pub start_value: Cell<T>,
pub target_value: Cell<T>,
pub value: Cell<T>,
pub active: Cell<bool>,
#[derivative(Debug = "ignore")]
pub tween_fn: F,
#[derivative(Debug = "ignore")]
pub callback: OnStep,
#[derivative(Debug = "ignore")]
pub on_end: OnEnd,
}
impl<T:Value,F,OnStep,OnEnd> AnimatorData<T,F,OnStep,OnEnd>
where F:AnyFnEasing, OnStep:Callback<T>, OnEnd:Callback<EndStatus> {
fn new(start:T, end:T, tween_fn:F, callback:OnStep, on_end:OnEnd) -> Self {
let duration = Cell::new(1000.0);
let value = Cell::new(start);
let start_value = Cell::new(start);
impl<T: Value, F, OnStep, OnEnd> AnimatorData<T, F, OnStep, OnEnd>
where
F: AnyFnEasing,
OnStep: Callback<T>,
OnEnd: Callback<EndStatus>,
{
fn new(start: T, end: T, tween_fn: F, callback: OnStep, on_end: OnEnd) -> Self {
let duration = Cell::new(1000.0);
let value = Cell::new(start);
let start_value = Cell::new(start);
let target_value = Cell::new(end);
let active = default();
Self {duration,start_value,target_value,value,active,tween_fn,callback,on_end}
let active = default();
Self { duration, start_value, target_value, value, active, tween_fn, callback, on_end }
}
fn step(&self, time:f32) {
let sample = (time / self.duration.get()).min(1.0);
let weight = (self.tween_fn)(sample);
let value = self.start_value.get() * (1.0-weight) + self.target_value.get() * weight;
fn step(&self, time: f32) {
let sample = (time / self.duration.get()).min(1.0);
let weight = (self.tween_fn)(sample);
let value = self.start_value.get() * (1.0 - weight) + self.target_value.get() * weight;
let finished = (sample - 1.0).abs() < std::f32::EPSILON;
self.callback.call(value);
self.value.set(value);
@ -215,18 +225,22 @@ where F:AnyFnEasing, OnStep:Callback<T>, OnEnd:Callback<EndStatus> {
}
}
impl<T:Value,F,OnStep,OnEnd> Animator<T,F,OnStep,OnEnd>
where F:AnyFnEasing, OnStep:Callback<T>, OnEnd:Callback<EndStatus> {
impl<T: Value, F, OnStep, OnEnd> Animator<T, F, OnStep, OnEnd>
where
F: AnyFnEasing,
OnStep: Callback<T>,
OnEnd: Callback<EndStatus>,
{
/// Constructor.
pub fn new_not_started(start:T, end:T, tween_fn:F, callback:OnStep, on_end:OnEnd) -> Self {
let data = Rc::new(AnimatorData::new(start,end,tween_fn,callback,on_end));
pub fn new_not_started(start: T, end: T, tween_fn: F, callback: OnStep, on_end: OnEnd) -> Self {
let data = Rc::new(AnimatorData::new(start, end, tween_fn, callback, on_end));
let animation_loop = default();
Self {data,animation_loop}
Self { data, animation_loop }
}
/// Constructor.
pub fn new(start:T, end:T, tween_fn:F, callback:OnStep, on_end:OnEnd) -> Self {
let this = Self::new_not_started(start,end,tween_fn,callback,on_end);
pub fn new(start: T, end: T, tween_fn: F, callback: OnStep, on_end: OnEnd) -> Self {
let this = Self::new_not_started(start, end, tween_fn, callback, on_end);
this.start();
this
}
@ -253,7 +267,7 @@ where F:AnyFnEasing, OnStep:Callback<T>, OnEnd:Callback<EndStatus> {
}
/// Stop the animation, rewind it to the provided value and call the callback.
pub fn stop_and_rewind_to(&self, value:T) {
pub fn stop_and_rewind_to(&self, value: T) {
self.stop();
self.set_start_value_no_restart(value);
self.data.value.set(value);
@ -272,7 +286,7 @@ where F:AnyFnEasing, OnStep:Callback<T>, OnEnd:Callback<EndStatus> {
}
/// Restart the animation using the current value as the new start value.
pub fn from_now_to(&self, tgt:T) {
pub fn from_now_to(&self, tgt: T) {
let current = self.value();
self.data.start_value.set(current);
self.data.target_value.set(tgt);
@ -285,7 +299,7 @@ where F:AnyFnEasing, OnStep:Callback<T>, OnEnd:Callback<EndStatus> {
/// Set the new target value. In case the target value is different from the current target
/// value, the animation will be restarted with the current value being the new start value.
pub fn set_target_value(&self, tgt:T) {
pub fn set_target_value(&self, tgt: T) {
if self.data.target_value.get() != tgt {
self.from_now_to(tgt)
}
@ -305,8 +319,12 @@ where F:AnyFnEasing, OnStep:Callback<T>, OnEnd:Callback<EndStatus> {
// === Getters & Setters ===
#[allow(missing_docs)]
impl<T:Value,F,OnStep,OnEnd> Animator<T,F,OnStep,OnEnd>
where F:AnyFnEasing, OnStep:Callback<T>, OnEnd:Callback<EndStatus> {
impl<T: Value, F, OnStep, OnEnd> Animator<T, F, OnStep, OnEnd>
where
F: AnyFnEasing,
OnStep: Callback<T>,
OnEnd: Callback<EndStatus>,
{
pub fn start_value(&self) -> T {
self.data.start_value.get()
}
@ -319,15 +337,15 @@ where F:AnyFnEasing, OnStep:Callback<T>, OnEnd:Callback<EndStatus> {
self.data.target_value.get()
}
pub fn set_start_value_no_restart(&self, t:T) {
pub fn set_start_value_no_restart(&self, t: T) {
self.data.start_value.set(t);
}
pub fn set_target_value_no_restart(&self, t:T) {
pub fn set_target_value_no_restart(&self, t: T) {
self.data.target_value.set(t);
}
pub fn set_duration(&self, t:f32) {
pub fn set_duration(&self, t: f32) {
self.data.duration.set(t);
}
}
@ -339,28 +357,28 @@ where F:AnyFnEasing, OnStep:Callback<T>, OnEnd:Callback<EndStatus> {
/// A wrapper over animation loop implementation. This type is defined mainly to make Rust type
/// inferencer happy (not infer infinite, recursive types).
#[derive(CloneRef,Derivative)]
#[derivative(Clone(bound=""))]
#[derivative(Default(bound=""))]
#[derive(CloneRef, Derivative)]
#[derivative(Clone(bound = ""))]
#[derivative(Default(bound = ""))]
#[allow(clippy::type_complexity)]
#[allow(missing_debug_implementations)]
pub struct AnimationLoop<T,F,OnStep,OnEnd> {
animation_loop : Rc<CloneCell<Option<AnimationStep<T,F,OnStep,OnEnd>>>>,
pub struct AnimationLoop<T, F, OnStep, OnEnd> {
animation_loop: Rc<CloneCell<Option<AnimationStep<T, F, OnStep, OnEnd>>>>,
}
#[allow(clippy::type_complexity)]
impl<T,F,OnStep,OnEnd> Deref for AnimationLoop<T,F,OnStep,OnEnd> {
type Target = Rc<CloneCell<Option<AnimationStep<T,F,OnStep,OnEnd>>>>;
impl<T, F, OnStep, OnEnd> Deref for AnimationLoop<T, F, OnStep, OnEnd> {
type Target = Rc<CloneCell<Option<AnimationStep<T, F, OnStep, OnEnd>>>>;
fn deref(&self) -> &Self::Target {
&self.animation_loop
}
}
impl<T,F,OnStep,OnEnd> AnimationLoop<T,F,OnStep,OnEnd> {
impl<T, F, OnStep, OnEnd> AnimationLoop<T, F, OnStep, OnEnd> {
/// Downgrade to a week reference.
pub fn downgrade(&self) -> WeakAnimationLoop<T,F,OnStep,OnEnd> {
pub fn downgrade(&self) -> WeakAnimationLoop<T, F, OnStep, OnEnd> {
let animation_loop = Rc::downgrade(&self.animation_loop);
WeakAnimationLoop {animation_loop}
WeakAnimationLoop { animation_loop }
}
}
@ -368,14 +386,14 @@ impl<T,F,OnStep,OnEnd> AnimationLoop<T,F,OnStep,OnEnd> {
/// inferencer happy (not infer infinite, recursive types).
#[allow(clippy::type_complexity)]
#[allow(missing_debug_implementations)]
pub struct WeakAnimationLoop<T,F,OnStep,OnEnd> {
animation_loop : Weak<CloneCell<Option<AnimationStep<T,F,OnStep,OnEnd>>>>,
pub struct WeakAnimationLoop<T, F, OnStep, OnEnd> {
animation_loop: Weak<CloneCell<Option<AnimationStep<T, F, OnStep, OnEnd>>>>,
}
impl<T,F,OnStep,OnEnd> WeakAnimationLoop<T,F,OnStep,OnEnd> {
impl<T, F, OnStep, OnEnd> WeakAnimationLoop<T, F, OnStep, OnEnd> {
/// Upgrade the weak reference.
pub fn upgrade(&self) -> Option<AnimationLoop<T,F,OnStep,OnEnd>> {
self.animation_loop.upgrade().map(|animation_loop| AnimationLoop{animation_loop})
pub fn upgrade(&self) -> Option<AnimationLoop<T, F, OnStep, OnEnd>> {
self.animation_loop.upgrade().map(|animation_loop| AnimationLoop { animation_loop })
}
}
@ -383,16 +401,21 @@ impl<T,F,OnStep,OnEnd> WeakAnimationLoop<T,F,OnStep,OnEnd> {
// === Animation Step ===
/// Alias for `FixedFrameRateLoop` with specified step callback.
pub type AnimationStep<T,F,OnStep,OnEnd> = animation::Loop<Step<T,F,OnStep,OnEnd>>;
pub type AnimationStep<T, F, OnStep, OnEnd> = animation::Loop<Step<T, F, OnStep, OnEnd>>;
/// Callback for an animation step.
pub type Step<T,F,OnStep,OnEnd> = impl Fn(animation::TimeInfo);
pub type Step<T, F, OnStep, OnEnd> = impl Fn(animation::TimeInfo);
fn step<T:Value,F,OnStep,OnEnd>(easing:&Animator<T,F,OnStep,OnEnd>) -> Step<T,F,OnStep,OnEnd>
where F:AnyFnEasing, OnStep:Callback<T>, OnEnd:Callback<EndStatus> {
let data = easing.data.clone_ref();
fn step<T: Value, F, OnStep, OnEnd>(
easing: &Animator<T, F, OnStep, OnEnd>,
) -> Step<T, F, OnStep, OnEnd>
where
F: AnyFnEasing,
OnStep: Callback<T>,
OnEnd: Callback<EndStatus>, {
let data = easing.data.clone_ref();
let animation_loop = easing.animation_loop.downgrade();
move |time:animation::TimeInfo| {
move |time: animation::TimeInfo| {
if data.active.get() {
data.step(time.local)
} else if let Some(animation_loop) = animation_loop.upgrade() {

View File

@ -1,7 +1,7 @@
//! FRP bindings to the animation engine.
pub mod hysteretic;
pub mod delayed;
pub mod hysteretic;
use crate::prelude::*;
@ -33,26 +33,27 @@ pub type AnimationSimulator<T> = inertia::DynSimulator<mix::Repr<T>>;
/// Smart animation handler. Contains of dynamic simulation and frp endpoint. Whenever a new value
/// is computed, it is emitted via the endpoint.
#[derive(CloneRef,Derivative,Debug)]
#[derivative(Clone(bound=""))]
#[derive(CloneRef, Derivative, Debug)]
#[derivative(Clone(bound = ""))]
#[allow(missing_docs)]
pub struct Animation<T:mix::Mixable+frp::Data> {
pub target : frp::Any<T>,
pub precision : frp::Any<f32>,
pub skip : frp::Any,
pub value : frp::Stream<T>,
pub struct Animation<T: mix::Mixable + frp::Data> {
pub target: frp::Any<T>,
pub precision: frp::Any<f32>,
pub skip: frp::Any,
pub value: frp::Stream<T>,
}
#[allow(missing_docs)]
impl<T:mix::Mixable+frp::Data> Animation<T>
where mix::Repr<T> : inertia::Value {
impl<T: mix::Mixable + frp::Data> Animation<T>
where mix::Repr<T>: inertia::Value
{
/// Constructor. The initial value of the animation is set to `default`.
pub fn new(network:&frp::Network) -> Self {
pub fn new(network: &frp::Network) -> Self {
frp::extend! { network
value_src <- any_mut::<T>();
}
let on_step = Box::new(f!((t) value_src.emit(mix::from_space::<T>(t))));
let simulator = AnimationSimulator::<T>::new(on_step,(),());
let on_step = Box::new(f!((t) value_src.emit(mix::from_space::<T>(t))));
let simulator = AnimationSimulator::<T>::new(on_step, (), ());
// FIXME[WD]: The precision should become default and should be increased in all simulators
// that work with pixels. The reason is that by default the simulator should
// give nice results for animations in the range of 0 .. 1, while it should not
@ -68,11 +69,11 @@ where mix::Repr<T> : inertia::Value {
}
let value = value_src.into();
network.store(&simulator);
Self{target,precision,skip,value}
Self { target, precision, skip, value }
}
/// Constructor. The initial value is provided explicitly.
pub fn new_with_init(network:&frp::Network, init:T) -> Self {
pub fn new_with_init(network: &frp::Network, init: T) -> Self {
let this = Self::new(network);
this.target.emit(init);
this.skip.emit(());
@ -81,7 +82,7 @@ where mix::Repr<T> : inertia::Value {
/// Constructor. There is no initial value. The first emitted `target` value will be used
/// without animation.
pub fn new_non_init(network:&frp::Network) -> Self {
pub fn new_non_init(network: &frp::Network) -> Self {
let this = Self::new(network);
frp::extend! { network
init <- any_mut();
@ -115,31 +116,32 @@ where mix::Repr<T> : inertia::Value {
/// any animation. This is different behavior from the previous implementation, where even
/// setting the value for the first time would create animation between `default()` value and the
/// new target. If your code depends on this behavior, it needs to be changed.
#[derive(CloneRef,Derivative,Debug,Shrinkwrap)]
#[derivative(Clone(bound=""))]
#[derive(CloneRef, Derivative, Debug, Shrinkwrap)]
#[derivative(Clone(bound = ""))]
#[allow(missing_docs)]
#[allow(non_camel_case_types)]
pub struct DEPRECATED_Animation<T:mix::Mixable> {
pub struct DEPRECATED_Animation<T: mix::Mixable> {
#[shrinkwrap(main_field)]
pub simulator : inertia::DynSimulator<T::Repr>,
pub value : frp::Stream<T>,
pub simulator: inertia::DynSimulator<T::Repr>,
pub value: frp::Stream<T>,
}
#[allow(missing_docs)]
impl<T:mix::Mixable+frp::Data> DEPRECATED_Animation<T>
where mix::Repr<T> : inertia::Value {
impl<T: mix::Mixable + frp::Data> DEPRECATED_Animation<T>
where mix::Repr<T>: inertia::Value
{
/// Constructor.
pub fn new(network:&frp::Network) -> Self {
pub fn new(network: &frp::Network) -> Self {
frp::extend! { network
def target = source::<T>();
}
let on_step = Box::new(f!((t) target.emit(mix::from_space::<T>(t))));
let simulator = inertia::DynSimulator::<T::Repr>::new(on_step,(),());
let on_step = Box::new(f!((t) target.emit(mix::from_space::<T>(t))));
let simulator = inertia::DynSimulator::<T::Repr>::new(on_step, (), ());
let value = target.into();
Self {simulator,value}
Self { simulator, value }
}
pub fn set_value(&self, value:T) {
pub fn set_value(&self, value: T) {
let animation_space_repr = value.into();
self.simulator.set_value(animation_space_repr.value);
}
@ -149,8 +151,8 @@ where mix::Repr<T> : inertia::Value {
mix::from_space(value)
}
pub fn set_target_value(&self, target_value:T) {
let state:mix::Space<_> = target_value.into();
pub fn set_target_value(&self, target_value: T) {
let state: mix::Space<_> = target_value.into();
self.simulator.set_target_value(state.value);
}

View File

@ -40,17 +40,17 @@ crate::define_endpoints! {
// =========================
/// Animation that has a delayed onset.
#[derive(Clone,CloneRef,Debug,Shrinkwrap)]
#[derive(Clone, CloneRef, Debug, Shrinkwrap)]
pub struct DelayedAnimation {
#[allow(missing_docs)]
pub frp : FrpEndpoints,
pub frp: FrpEndpoints,
}
impl DelayedAnimation {
#[allow(missing_docs)]
pub fn new(network:&frp::Network) -> Self {
let frp = Frp::extend(network);
let delay = Easing::new(network);
pub fn new(network: &frp::Network) -> Self {
let frp = Frp::extend(network);
let delay = Easing::new(network);
let transition = Easing::new(network);
frp::extend! { network
@ -79,6 +79,6 @@ impl DelayedAnimation {
(t - 1.0).abs() < std::f32::EPSILON).on_true();
}
Self{frp}
Self { frp }
}
}

View File

@ -16,7 +16,6 @@
//! the time the cursor is between icons is less than the `end_delay_duration`. Instead, the hiding
//! will only start iof the cursos has left any icon triggering the pop-up for longer than the
//! `end_delay_duration`.
//!
use crate::prelude::*;
use crate::Animation;
@ -54,22 +53,22 @@ crate::define_endpoints! {
// ===========================
/// Animation that has a delayed onset and offset.
#[derive(Clone,CloneRef,Debug,Shrinkwrap)]
#[derive(Clone, CloneRef, Debug, Shrinkwrap)]
pub struct HystereticAnimation {
#[allow(missing_docs)]
pub frp : FrpEndpoints,
pub frp: FrpEndpoints,
}
impl HystereticAnimation {
#[allow(missing_docs)]
pub fn new(network:&frp::Network, start_delay_duration:f32, end_delay_duration:f32) -> Self {
let frp = Frp::extend(network);
pub fn new(network: &frp::Network, start_delay_duration: f32, end_delay_duration: f32) -> Self {
let frp = Frp::extend(network);
let start_delay = Easing::new(network);
let end_delay = Easing::new(network);
let end_delay = Easing::new(network);
start_delay.set_duration(start_delay_duration);
end_delay.set_duration(end_delay_duration);
let transition = Animation::<f32>::new(network);
let transition = Animation::<f32>::new(network);
frp::extend! { network
@ -108,6 +107,6 @@ impl HystereticAnimation {
frp.source.on_start <+ offset_end;
}
HystereticAnimation{frp}
HystereticAnimation { frp }
}
}

View File

@ -26,10 +26,10 @@ crate::define_endpoints! {
/// Easing FRP animator. To learn more about easing functions, follow the link:
/// https://easings.net/en.
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
#[allow(missing_docs)]
pub struct Easing {
pub frp : FrpEndpoints,
pub frp: FrpEndpoints,
}
impl Deref for Easing {
@ -41,18 +41,21 @@ impl Deref for Easing {
impl Easing {
/// Constructor.
pub fn new(network:&frp::Network) -> Self {
let frp = Frp::extend(network);
let easing = easing::quad_in_out();
let on_step = Box::new(f!((t) frp.source.value.emit(t)));
let on_end = Box::new(f!((t) frp.source.on_end.emit(t)));
let animator = easing::DynAnimator::new_not_started(0.0,1.0,easing,on_step,on_end);
pub fn new(network: &frp::Network) -> Self {
let frp = Frp::extend(network);
let easing = easing::quad_in_out();
let on_step = Box::new(f!((t) frp.source.value.emit(t)));
let on_end = Box::new(f!((t) frp.source.on_end.emit(t)));
let animator = easing::DynAnimator::new_not_started(0.0, 1.0, easing, on_step, on_end);
network.store(&animator);
Self {frp} . init(network,&animator)
Self { frp }.init(network, &animator)
}
fn init
(self, network:&frp::Network, animator:&easing::DynAnimator<f32,easing::QuadInOut>) -> Self {
fn init(
self,
network: &frp::Network,
animator: &easing::DynAnimator<f32, easing::QuadInOut>,
) -> Self {
let frp = &self.frp;
frp::extend! { network
eval frp.set_duration ((t) animator.set_duration(*t));
@ -75,26 +78,26 @@ impl Easing {
/// # DEPRECATION
/// This component is deprecated. Use `Easing` instead, which exposes much more FRP-oriented API
/// than this component.
#[derive(Clone,CloneRef,Debug,Shrinkwrap)]
#[derive(Clone, CloneRef, Debug, Shrinkwrap)]
#[allow(missing_docs)]
#[allow(non_camel_case_types)]
pub struct DEPRECATED_Tween {
#[shrinkwrap(main_field)]
pub animator : easing::DynAnimator<f32,easing::QuadInOut>,
pub value : frp::Stream<f32>,
pub animator: easing::DynAnimator<f32, easing::QuadInOut>,
pub value: frp::Stream<f32>,
}
impl DEPRECATED_Tween {
/// Constructor.
pub fn new(network:&frp::Network) -> Self {
pub fn new(network: &frp::Network) -> Self {
frp::extend! { network
def target = source::<f32>();
}
let f = easing::quad_in_out();
let on_step = Box::new(f!((t) target.emit(t)));
let on_end = Box::new(|_|{});
let animator = easing::DynAnimator::new_not_started(0.0,1.0,f,on_step,on_end);
let value = target.into();
Self {animator,value}
let f = easing::quad_in_out();
let on_step = Box::new(f!((t) target.emit(t)));
let on_end = Box::new(|_| {});
let animator = easing::DynAnimator::new_not_started(0.0, 1.0, f, on_step, on_end);
let value = target.into();
Self { animator, value }
}
}

View File

@ -19,14 +19,14 @@ use wasm_bindgen::prelude::Closure;
/// differ across browsers and browser versions. We have even observed that `performance.now()` can
/// sometimes provide a bigger value than time provided to `requestAnimationFrame` callback later,
/// which resulted in a negative frame time.
#[derive(Clone,Copy,Debug,Default)]
#[derive(Clone, Copy, Debug, Default)]
pub struct TimeInfo {
/// Start time of the animation loop.
pub start : f32,
pub start: f32,
/// The last frame time.
pub frame : f32,
pub frame: f32,
/// The time which passed since the animation loop was started.
pub local : f32,
pub local: f32,
}
impl TimeInfo {
@ -54,49 +54,50 @@ pub trait RawLoopCallback = FnMut(f32) + 'static;
/// an animation loop, you are probably looking for the `Loop` which adds slight complexity
/// in order to provide better time information. The complexity is so small that it would not be
/// noticeable in almost any use case.
#[derive(CloneRef,Derivative)]
#[derivative(Clone(bound=""))]
#[derivative(Debug(bound=""))]
#[derive(CloneRef, Derivative)]
#[derivative(Clone(bound = ""))]
#[derivative(Debug(bound = ""))]
pub struct RawLoop<Callback> {
data: Rc<RefCell<RawLoopData<Callback>>>,
}
impl<Callback> RawLoop<Callback>
where Callback : RawLoopCallback {
where Callback: RawLoopCallback
{
/// Create and start a new animation loop.
pub fn new(callback:Callback) -> Self {
let data = Rc::new(RefCell::new(RawLoopData::new(callback)));
pub fn new(callback: Callback) -> Self {
let data = Rc::new(RefCell::new(RawLoopData::new(callback)));
let weak_data = Rc::downgrade(&data);
let on_frame = move |time| weak_data.upgrade().for_each(|t| t.borrow_mut().run(time));
let on_frame = move |time| weak_data.upgrade().for_each(|t| t.borrow_mut().run(time));
data.borrow_mut().on_frame = Some(Closure::new(on_frame));
let handle_id = web::request_animation_frame(data.borrow_mut().on_frame.as_ref().unwrap());
data.borrow_mut().handle_id = handle_id;
Self {data}
Self { data }
}
}
/// The internal state of the `RawLoop`.
#[derive(Derivative)]
#[derivative(Debug(bound=""))]
#[derivative(Debug(bound = ""))]
pub struct RawLoopData<Callback> {
#[derivative(Debug="ignore")]
callback : Callback,
on_frame : Option<Closure<dyn FnMut(f64)>>,
handle_id : i32,
#[derivative(Debug = "ignore")]
callback: Callback,
on_frame: Option<Closure<dyn FnMut(f64)>>,
handle_id: i32,
}
impl<Callback> RawLoopData<Callback> {
/// Constructor.
fn new(callback:Callback) -> Self {
let on_frame = default();
fn new(callback: Callback) -> Self {
let on_frame = default();
let handle_id = default();
Self {callback,on_frame,handle_id}
Self { callback, on_frame, handle_id }
}
/// Run the animation frame.
fn run(&mut self, current_time_ms:f64)
where Callback:FnMut(f32) {
let callback = &mut self.callback;
fn run(&mut self, current_time_ms: f64)
where Callback: FnMut(f32) {
let callback = &mut self.callback;
self.handle_id = self.on_frame.as_ref().map_or(default(), |on_frame| {
callback(current_time_ms as f32);
web::request_animation_frame(on_frame)
@ -126,34 +127,40 @@ pub trait LoopCallback = FnMut(TimeInfo) + 'static;
/// An animation loop. Runs the provided `Callback` every animation frame. It uses the
/// `RawLoop` under the hood. If you are looking for a more complex version where you can
/// register new callbacks for every frame, take a look at the ``.
#[derive(CloneRef,Derivative)]
#[derivative(Clone(bound=""))]
#[derivative(Debug(bound=""))]
#[derive(CloneRef, Derivative)]
#[derivative(Clone(bound = ""))]
#[derivative(Debug(bound = ""))]
pub struct Loop<Callback> {
animation_loop : RawLoop<OnFrame<Callback>>,
time_info : Rc<Cell<TimeInfo>>,
animation_loop: RawLoop<OnFrame<Callback>>,
time_info: Rc<Cell<TimeInfo>>,
}
impl<Callback> Loop<Callback>
where Callback : LoopCallback {
where Callback: LoopCallback
{
/// Constructor.
pub fn new(callback:Callback) -> Self {
let time_info = Rc::new(Cell::new(TimeInfo::new()));
let animation_loop = RawLoop::new(on_frame(callback,time_info.clone_ref()));
Self {animation_loop,time_info}
pub fn new(callback: Callback) -> Self {
let time_info = Rc::new(Cell::new(TimeInfo::new()));
let animation_loop = RawLoop::new(on_frame(callback, time_info.clone_ref()));
Self { animation_loop, time_info }
}
}
/// Callback for an animation frame.
pub type OnFrame<Callback> = impl FnMut(f32);
fn on_frame<Callback>(mut callback:Callback, time_info_ref:Rc<Cell<TimeInfo>>) -> OnFrame<Callback>
where Callback : LoopCallback {
move |current_time:f32| {
fn on_frame<Callback>(
mut callback: Callback,
time_info_ref: Rc<Cell<TimeInfo>>,
) -> OnFrame<Callback>
where
Callback: LoopCallback,
{
move |current_time: f32| {
let time_info = time_info_ref.get();
let start = if time_info.start == 0.0 {current_time} else {time_info.start};
let frame = current_time - start - time_info.local;
let local = current_time - start;
let time_info = TimeInfo {start,frame,local};
let start = if time_info.start == 0.0 { current_time } else { time_info.start };
let frame = current_time - start - time_info.local;
let local = current_time - start;
let time_info = TimeInfo { start, frame, local };
time_info_ref.set(time_info);
callback(time_info);
}
@ -168,45 +175,45 @@ where Callback : LoopCallback {
/// A callback `FnMut(TimeInfo) -> FnMut(TimeInfo)` transformer. Calls the inner callback with a
/// constant frame rate.
#[derive(Derivative)]
#[derivative(Debug(bound=""))]
#[derivative(Debug(bound = ""))]
pub struct FixedFrameRateSampler<Callback> {
frame_time : f32,
local_time : f32,
time_buffer : f32,
#[derivative(Debug="ignore")]
callback : Callback,
frame_time: f32,
local_time: f32,
time_buffer: f32,
#[derivative(Debug = "ignore")]
callback: Callback,
}
impl<Callback> FixedFrameRateSampler<Callback> {
/// Constructor.
pub fn new(frame_rate:f32, callback:Callback) -> Self {
let frame_time = 1000.0 / frame_rate;
let local_time = default();
pub fn new(frame_rate: f32, callback: Callback) -> Self {
let frame_time = 1000.0 / frame_rate;
let local_time = default();
let time_buffer = default();
Self {frame_time,local_time,time_buffer,callback}
Self { frame_time, local_time, time_buffer, callback }
}
}
impl<Callback:FnOnce<(TimeInfo,)>> FnOnce<(TimeInfo,)> for FixedFrameRateSampler<Callback> {
impl<Callback: FnOnce<(TimeInfo,)>> FnOnce<(TimeInfo,)> for FixedFrameRateSampler<Callback> {
type Output = ();
extern "rust-call" fn call_once(self, args:(TimeInfo,)) -> Self::Output {
extern "rust-call" fn call_once(self, args: (TimeInfo,)) -> Self::Output {
self.callback.call_once(args);
}
}
impl<Callback:FnMut<(TimeInfo,)>> FnMut<(TimeInfo,)> for FixedFrameRateSampler<Callback> {
extern "rust-call" fn call_mut(&mut self, args:(TimeInfo,)) -> Self::Output {
impl<Callback: FnMut<(TimeInfo,)>> FnMut<(TimeInfo,)> for FixedFrameRateSampler<Callback> {
extern "rust-call" fn call_mut(&mut self, args: (TimeInfo,)) -> Self::Output {
let time = args.0;
self.time_buffer += time.frame;
loop {
if self.time_buffer < 0.0 {
break
break;
} else {
self.time_buffer -= self.frame_time;
let start = time.start;
let frame = self.frame_time;
let local = self.local_time;
let time2 = TimeInfo {start,frame,local};
let time2 = TimeInfo { start, frame, local };
self.local_time += self.frame_time;
self.callback.call_mut((time2,));
}
@ -224,10 +231,11 @@ impl<Callback:FnMut<(TimeInfo,)>> FnMut<(TimeInfo,)> for FixedFrameRateSampler<C
pub type FixedFrameRateLoop<Callback> = Loop<FixedFrameRateSampler<Callback>>;
impl<Callback> FixedFrameRateLoop<Callback>
where Callback:LoopCallback {
where Callback: LoopCallback
{
/// Constructor.
pub fn new_with_fixed_frame_rate(frame_rate:f32, callback:Callback) -> Self {
Self::new(FixedFrameRateSampler::new(frame_rate,callback))
pub fn new_with_fixed_frame_rate(frame_rate: f32, callback: Callback) -> Self {
Self::new(FixedFrameRateSampler::new(frame_rate, callback))
}
}
@ -247,34 +255,33 @@ pub trait DynamicLoopCallback = callback::CopyCallbackMut1Fn<TimeInfo>;
///
/// Please refer to `Loop` if you don't need the ability to add / remove callbacks dynamically and
/// you want better performance.
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
pub struct DynamicLoop {
raw_loop : Loop<Box<dyn FnMut(TimeInfo)>>,
data : Rc<RefCell<DynamicLoopData>>,
raw_loop: Loop<Box<dyn FnMut(TimeInfo)>>,
data: Rc<RefCell<DynamicLoopData>>,
}
/// Internal representation for `DynamicLoop`.
#[derive(Debug,Default)]
#[derive(Debug, Default)]
pub struct DynamicLoopData {
on_frame : callback::CopyRegistry1<TimeInfo>,
on_before_frame : callback::CopyRegistry1<TimeInfo>,
on_after_frame : callback::CopyRegistry1<TimeInfo>,
on_frame: callback::CopyRegistry1<TimeInfo>,
on_before_frame: callback::CopyRegistry1<TimeInfo>,
on_after_frame: callback::CopyRegistry1<TimeInfo>,
}
impl Default for DynamicLoop {
fn default() -> Self {
let data = Rc::new(RefCell::new(DynamicLoopData::default()));
let weak = Rc::downgrade(&data);
let raw_loop : Loop<Box<dyn FnMut(TimeInfo)>> =
Loop::new(Box::new(move |time| {
weak.upgrade().for_each(|data| {
let mut data_mut = data.borrow_mut();
data_mut.on_before_frame.run_all(time);
data_mut.on_frame.run_all(time);
data_mut.on_after_frame.run_all(time);
})
}));
Self {raw_loop,data}
let raw_loop: Loop<Box<dyn FnMut(TimeInfo)>> = Loop::new(Box::new(move |time| {
weak.upgrade().for_each(|data| {
let mut data_mut = data.borrow_mut();
data_mut.on_before_frame.run_all(time);
data_mut.on_frame.run_all(time);
data_mut.on_after_frame.run_all(time);
})
}));
Self { raw_loop, data }
}
}
@ -285,17 +292,17 @@ impl DynamicLoop {
}
/// Add new callback which will be run on every animation frame.
pub fn on_frame<F:DynamicLoopCallback>(&self, callback:F) -> callback::Handle {
pub fn on_frame<F: DynamicLoopCallback>(&self, callback: F) -> callback::Handle {
self.data.borrow_mut().on_frame.add(Box::new(callback))
}
/// Add new callback which will be run on before all callbacks registered with `on_frame`.
pub fn on_before_frame<F:DynamicLoopCallback>(&self, callback:F) -> callback::Handle {
pub fn on_before_frame<F: DynamicLoopCallback>(&self, callback: F) -> callback::Handle {
self.data.borrow_mut().on_before_frame.add(Box::new(callback))
}
/// Add new callback which will be run on after all callbacks registered with `on_frame`.
pub fn on_after_frame<F:DynamicLoopCallback>(&self, callback:F) -> callback::Handle {
pub fn on_after_frame<F: DynamicLoopCallback>(&self, callback: F) -> callback::Handle {
self.data.borrow_mut().on_after_frame.add(Box::new(callback))
}
}

View File

@ -16,13 +16,14 @@ use crate::data::function::Fn1;
/// The type of the value of the simulation. In particular, the Value could be `f32`
/// (1-dimensional simulation), or `Vector3<f32>` (3-dimensional simulation).
pub trait Value
= 'static + Copy + Default
pub trait Value = 'static
+ Copy
+ Default
+ PartialEq
+ Normalize
+ Magnitude <Output=f32>
+ Add <Output=Self>
+ Mul <f32,Output=Self>;
+ Magnitude<Output = f32>
+ Add<Output = Self>
+ Mul<f32, Output = Self>;
@ -34,112 +35,112 @@ macro_rules! define_f32_opr_mods {
($name:ident $opr:ident $f:ident) => {
define_f32_opr_mods_lhs! {$name $opr $f}
define_f32_opr_mods_rhs! {$name $opr $f}
}
};
}
macro_rules! define_f32_opr_mods_lhs {
($name:ident $opr:ident $f:ident) => {
impl $opr<$name> for f32 {
type Output = $name;
fn $f(self, rhs:$name) -> $name {
$name{value : self.$f(rhs.value)}
fn $f(self, rhs: $name) -> $name {
$name { value: self.$f(rhs.value) }
}
}
impl $opr<&$name> for f32 {
type Output = $name;
fn $f(self, rhs:&$name) -> $name {
$name{value : self.$f(rhs.value)}
fn $f(self, rhs: &$name) -> $name {
$name { value: self.$f(rhs.value) }
}
}
impl $opr<$name> for &f32 {
type Output = $name;
fn $f(self, rhs:$name) -> $name {
$name{value : self.$f(rhs.value)}
fn $f(self, rhs: $name) -> $name {
$name { value: self.$f(rhs.value) }
}
}
impl $opr<&$name> for &f32 {
type Output = $name;
fn $f(self, rhs:&$name) -> $name {
$name{value : self.$f(rhs.value)}
fn $f(self, rhs: &$name) -> $name {
$name { value: self.$f(rhs.value) }
}
}
}
};
}
macro_rules! define_f32_opr_mods_rhs {
($name:ident $opr:ident $f:ident) => {
impl $opr<f32> for $name {
type Output = $name;
fn $f(self, rhs:f32) -> $name {
$name{value : self.value.$f(rhs)}
fn $f(self, rhs: f32) -> $name {
$name { value: self.value.$f(rhs) }
}
}
impl $opr<&f32> for $name {
type Output = $name;
fn $f(self, rhs:&f32) -> $name {
$name{value : self.value.$f(rhs)}
fn $f(self, rhs: &f32) -> $name {
$name { value: self.value.$f(rhs) }
}
}
impl $opr<f32> for &$name {
type Output = $name;
fn $f(self, rhs:f32) -> $name {
$name{value : self.value.$f(rhs)}
fn $f(self, rhs: f32) -> $name {
$name { value: self.value.$f(rhs) }
}
}
impl $opr<&f32> for &$name {
type Output = $name;
fn $f(self, rhs:&f32) -> $name {
$name{value : self.value.$f(rhs)}
fn $f(self, rhs: &f32) -> $name {
$name { value: self.value.$f(rhs) }
}
}
}
};
}
macro_rules! define_self_opr_mods {
($name:ident $opr:ident $f:ident) => {
impl $opr<$name> for $name {
type Output = $name;
fn $f(self, rhs:$name) -> $name {
$name{value : self.value.$f(rhs.value)}
fn $f(self, rhs: $name) -> $name {
$name { value: self.value.$f(rhs.value) }
}
}
impl $opr<&$name> for $name {
type Output = $name;
fn $f(self, rhs:&$name) -> $name {
$name{value : self.value.$f(rhs.value)}
fn $f(self, rhs: &$name) -> $name {
$name { value: self.value.$f(rhs.value) }
}
}
impl $opr<$name> for &$name {
type Output = $name;
fn $f(self, rhs:$name) -> $name {
$name{value : self.value.$f(rhs.value)}
fn $f(self, rhs: $name) -> $name {
$name { value: self.value.$f(rhs.value) }
}
}
impl $opr<&$name> for &$name {
type Output = $name;
fn $f(self, rhs:&$name) -> $name {
$name{value : self.value.$f(rhs.value)}
fn $f(self, rhs: &$name) -> $name {
$name { value: self.value.$f(rhs.value) }
}
}
}
};
}
macro_rules! define_property {
($name:ident = $default:expr) => {
/// SimulationDataCell property.
#[derive(Debug,Clone,Copy,Into,From)]
#[derive(Debug, Clone, Copy, Into, From)]
pub struct $name {
/// Internal value of the $name.
pub value : f32,
pub value: f32,
}
impl $name {
@ -152,13 +153,13 @@ macro_rules! define_property {
impl Default for $name {
fn default() -> Self {
let value = $default;
Self {value}
Self { value }
}
}
define_self_opr_mods! {$name Add add}
define_self_opr_mods! {$name Sub sub}
define_f32_opr_mods! {$name Mul mul}
define_self_opr_mods! {$name Add add}
define_self_opr_mods! {$name Sub sub}
define_f32_opr_mods! {$name Mul mul}
define_f32_opr_mods_rhs! {$name Div div}
};
}
@ -174,23 +175,23 @@ define_property! { Mass = 30.0 }
// ==================
/// Thresholds defining the values which define when simulation stops.
#[derive(Clone,Copy,Debug)]
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub struct Thresholds {
pub distance : f32,
pub speed : f32
pub distance: f32,
pub speed: f32,
}
impl Default for Thresholds {
fn default() -> Self {
Self::new(0.1,0.1)
Self::new(0.1, 0.1)
}
}
impl Thresholds {
/// Constructor.
pub fn new(distance:f32, speed:f32) -> Self {
Self {distance,speed}
pub fn new(distance: f32, speed: f32) -> Self {
Self { distance, speed }
}
}
@ -218,56 +219,56 @@ impl Thresholds {
/// acceleration = force / self.mass;
/// new_velocity = velocity + acceleration * delta_time;
/// ```
#[derive(Clone,Copy,Debug,Default)]
#[derive(Clone, Copy, Debug, Default)]
pub struct SimulationData<T> {
// We store the current value as an offset from the target rather than an absolute value. This
// reduces numerical errors when animating floating point numbers: The offset will become very
// small towards the end of the animation. Small floating point numbers offer higher precision
// than large ones, because more digits can be used behind the point, for the fractional part of
// the number. This higher precision helps us to avoid non-termination that could otherwise
// happen due to rounding errors in our `step` function.
// than large ones, because more digits can be used behind the point, for the fractional part
// of the number. This higher precision helps us to avoid non-termination that could
// otherwise happen due to rounding errors in our `step` function.
//
// For example: The precision of `f32` values is so low that we can only represent every second
// integer above 16 777 216. If we simulate values that large and represented the simulation's
// state by its current total value then this internal state would have to jump over those gaps.
// The animation would either become to fast (if we rounded the steps up) or slow down too early
// (if we rounded the steps down). Generally, it would be difficult to handle the rounding
// errors gracefully. By representing the state as an offset, we achieve the highest possible
// precision as the animation approaches its target. Large rounding errors might only happen
// when the simulation is still far away from the target. But in those situations, high
// precision is not as important.
offset_from_target : T,
target_value : T,
velocity : T,
mass : Mass,
spring : Spring,
drag : Drag,
thresholds : Thresholds,
active : bool,
// state by its current total value then this internal state would have to jump over those
// gaps. The animation would either become to fast (if we rounded the steps up) or slow
// down too early (if we rounded the steps down). Generally, it would be difficult to
// handle the rounding errors gracefully. By representing the state as an offset, we
// achieve the highest possible precision as the animation approaches its target. Large
// rounding errors might only happen when the simulation is still far away from the target.
// But in those situations, high precision is not as important.
offset_from_target: T,
target_value: T,
velocity: T,
mass: Mass,
spring: Spring,
drag: Drag,
thresholds: Thresholds,
active: bool,
}
impl<T:Value> SimulationData<T> {
impl<T: Value> SimulationData<T> {
/// Constructor.
pub fn new() -> Self {
default()
}
/// Runs a simulation step.
fn step(&mut self, delta_seconds:f32) {
fn step(&mut self, delta_seconds: f32) {
if self.active {
let velocity = self.velocity.magnitude();
let distance = self.offset_from_target.magnitude();
let velocity = self.velocity.magnitude();
let distance = self.offset_from_target.magnitude();
let snap_velocity = velocity < self.thresholds.speed;
let snap_distance = distance < self.thresholds.distance;
let should_snap = snap_velocity && snap_distance;
let should_snap = snap_velocity && snap_distance;
if should_snap || distance.is_nan() {
self.offset_from_target = default();
self.velocity = default();
self.active = false;
self.velocity = default();
self.active = false;
} else {
let force = self.spring_force() + self.drag_force();
let acceleration = force * (1.0 / self.mass.value);
self.velocity = self.velocity + acceleration * delta_seconds;
let force = self.spring_force() + self.drag_force();
let acceleration = force * (1.0 / self.mass.value);
self.velocity = self.velocity + acceleration * delta_seconds;
self.offset_from_target = self.offset_from_target + self.velocity * delta_seconds;
}
}
@ -289,8 +290,8 @@ impl<T:Value> SimulationData<T> {
/// moving things on the stage with a single pixel being your unit value, you should set the
/// precision to 0.1. In case of a color alpha animation between 0.0 and 1.0, two digits after
/// the dot are important, so your precision should be 0.001.
fn set_precision(&mut self, precision:f32) {
self.thresholds.speed = precision;
fn set_precision(&mut self, precision: f32) {
self.thresholds.speed = precision;
self.thresholds.distance = precision;
}
}
@ -299,75 +300,101 @@ impl<T:Value> SimulationData<T> {
// === Getters ===
#[allow(missing_docs)]
impl<T:Value> SimulationData<T> {
pub fn value (&self) -> T { self.target_value + self.offset_from_target }
pub fn target_value (&self) -> T { self.target_value }
pub fn velocity (&self) -> T { self.velocity }
pub fn mass (&self) -> Mass { self.mass }
pub fn spring (&self) -> Spring { self.spring }
pub fn drag (&self) -> Drag { self.drag }
pub fn thresholds (&self) -> Thresholds { self.thresholds }
pub fn active (&self) -> bool { self.active }
impl<T: Value> SimulationData<T> {
pub fn value(&self) -> T {
self.target_value + self.offset_from_target
}
pub fn target_value(&self) -> T {
self.target_value
}
pub fn velocity(&self) -> T {
self.velocity
}
pub fn mass(&self) -> Mass {
self.mass
}
pub fn spring(&self) -> Spring {
self.spring
}
pub fn drag(&self) -> Drag {
self.drag
}
pub fn thresholds(&self) -> Thresholds {
self.thresholds
}
pub fn active(&self) -> bool {
self.active
}
}
// === Setters ===
#[allow(missing_docs)]
impl<T:Value> SimulationData<T> {
pub fn set_velocity (&mut self, velocity:T) { self.velocity = velocity; }
pub fn set_mass (&mut self, mass:Mass) { self.mass = mass; }
pub fn set_spring (&mut self, spring:Spring) { self.spring = spring; }
pub fn set_drag (&mut self, drag:Drag) { self.drag = drag; }
pub fn set_thresholds (&mut self, thresholds:Thresholds) { self.thresholds = thresholds; }
impl<T: Value> SimulationData<T> {
pub fn set_velocity(&mut self, velocity: T) {
self.velocity = velocity;
}
pub fn set_mass(&mut self, mass: Mass) {
self.mass = mass;
}
pub fn set_spring(&mut self, spring: Spring) {
self.spring = spring;
}
pub fn set_drag(&mut self, drag: Drag) {
self.drag = drag;
}
pub fn set_thresholds(&mut self, thresholds: Thresholds) {
self.thresholds = thresholds;
}
pub fn set_value(&mut self, value:T) {
pub fn set_value(&mut self, value: T) {
self.active = true;
self.offset_from_target = value + self.target_value * -1.0;
}
pub fn set_target_value(&mut self, target_value:T) {
pub fn set_target_value(&mut self, target_value: T) {
self.active = true;
let old_target_value = self.target_value;
self.target_value = target_value;
self.offset_from_target = old_target_value + self.offset_from_target + target_value * -1.0;
}
pub fn update_value<F:FnOnce(T)->T>(&mut self, f:F) {
pub fn update_value<F: FnOnce(T) -> T>(&mut self, f: F) {
self.set_value(f(self.value()));
}
pub fn update_target_value<F:FnOnce(T)->T>(&mut self, f:F) {
pub fn update_target_value<F: FnOnce(T) -> T>(&mut self, f: F) {
self.set_target_value(f(self.target_value()));
}
pub fn update_velocity<F:FnOnce(T)->T>(&mut self, f:F) {
pub fn update_velocity<F: FnOnce(T) -> T>(&mut self, f: F) {
self.set_velocity(f(self.velocity()));
}
pub fn update_mass<F:FnOnce(Mass)->Mass>(&mut self, f:F) {
pub fn update_mass<F: FnOnce(Mass) -> Mass>(&mut self, f: F) {
self.set_mass(f(self.mass()));
}
pub fn update_spring<F:FnOnce(Spring)->Spring>(&mut self, f:F) -> Spring {
pub fn update_spring<F: FnOnce(Spring) -> Spring>(&mut self, f: F) -> Spring {
let value = f(self.spring());
self.set_spring(value);
value
}
pub fn update_drag<F:FnOnce(Drag)->Drag>(&mut self, f:F) {
pub fn update_drag<F: FnOnce(Drag) -> Drag>(&mut self, f: F) {
self.set_drag(f(self.drag()));
}
pub fn update_thresholds<F:FnOnce(Thresholds)->Thresholds>(&mut self, f:F) {
pub fn update_thresholds<F: FnOnce(Thresholds) -> Thresholds>(&mut self, f: F) {
self.set_thresholds(f(self.thresholds()));
}
/// Stop the animator and set it to the target value.
pub fn skip(&mut self) {
self.active = false;
self.active = false;
self.offset_from_target = default();
self.velocity = default();
self.velocity = default();
}
}
@ -379,20 +406,20 @@ impl<T:Value> SimulationData<T> {
/// The main simulation engine. It allows running the simulation by explicitly calling the `step`
/// function. Refer to `Simulator` for a more automated solution.
#[derive(Derivative,Default)]
#[derivative(Debug(bound="T:Copy+Debug"))]
#[derive(Derivative, Default)]
#[derivative(Debug(bound = "T:Copy+Debug"))]
pub struct SimulationDataCell<T> {
data : Cell<SimulationData<T>>
data: Cell<SimulationData<T>>,
}
impl<T:Value> SimulationDataCell<T> {
impl<T: Value> SimulationDataCell<T> {
/// Constructor.
pub fn new() -> Self {
default()
}
/// Runs a simulation step.
pub fn step(&self, delta_seconds:f32) {
pub fn step(&self, delta_seconds: f32) {
let mut data = self.data.get();
data.step(delta_seconds);
self.data.set(data);
@ -403,7 +430,7 @@ impl<T:Value> SimulationDataCell<T> {
// === Getters ===
#[allow(missing_docs)]
impl<T:Value> SimulationDataCell<T> {
impl<T: Value> SimulationDataCell<T> {
pub fn active(&self) -> bool {
self.data.get().active()
}
@ -433,53 +460,89 @@ impl<T:Value> SimulationDataCell<T> {
// === Setters ===
#[allow(missing_docs)]
impl<T:Value> SimulationDataCell<T> {
pub fn set_drag(&self, drag:Drag) {
self.data.update(|mut sim| {sim.set_drag(drag); sim});
impl<T: Value> SimulationDataCell<T> {
pub fn set_drag(&self, drag: Drag) {
self.data.update(|mut sim| {
sim.set_drag(drag);
sim
});
}
pub fn update_drag<F:FnOnce(Drag)->Drag>(&self, f:F) {
self.data.update(|mut sim| {sim.update_drag(f); sim});
pub fn update_drag<F: FnOnce(Drag) -> Drag>(&self, f: F) {
self.data.update(|mut sim| {
sim.update_drag(f);
sim
});
}
pub fn set_spring(&self, spring:Spring) {
self.data.update(|mut sim| {sim.set_spring(spring); sim});
pub fn set_spring(&self, spring: Spring) {
self.data.update(|mut sim| {
sim.set_spring(spring);
sim
});
}
pub fn update_spring<F:FnOnce(Spring)->Spring>(&self, f:F) {
self.data.update(|mut sim| {sim.update_spring(f); sim});
pub fn update_spring<F: FnOnce(Spring) -> Spring>(&self, f: F) {
self.data.update(|mut sim| {
sim.update_spring(f);
sim
});
}
pub fn set_mass(&self, mass:Mass) {
self.data.update(|mut sim| {sim.set_mass(mass); sim});
pub fn set_mass(&self, mass: Mass) {
self.data.update(|mut sim| {
sim.set_mass(mass);
sim
});
}
pub fn update_mass<F:FnOnce(Mass)->Mass>(&self, f:F) {
self.data.update(|mut sim| {sim.update_mass(f); sim});
pub fn update_mass<F: FnOnce(Mass) -> Mass>(&self, f: F) {
self.data.update(|mut sim| {
sim.update_mass(f);
sim
});
}
pub fn set_velocity(&self, velocity:T) {
self.data.update(|mut sim| {sim.set_velocity(velocity); sim});
pub fn set_velocity(&self, velocity: T) {
self.data.update(|mut sim| {
sim.set_velocity(velocity);
sim
});
}
pub fn set_value(&self, value:T) {
self.data.update(|mut sim| {sim.set_value(value); sim});
pub fn set_value(&self, value: T) {
self.data.update(|mut sim| {
sim.set_value(value);
sim
});
}
pub fn set_target_value(&self, target_value:T) {
self.data.update(|mut sim| {sim.set_target_value(target_value); sim});
pub fn set_target_value(&self, target_value: T) {
self.data.update(|mut sim| {
sim.set_target_value(target_value);
sim
});
}
pub fn update_target_value<F:FnOnce(T)->T>(&self, f:F) {
self.data.update(|mut sim| {sim.update_target_value(f); sim});
pub fn update_target_value<F: FnOnce(T) -> T>(&self, f: F) {
self.data.update(|mut sim| {
sim.update_target_value(f);
sim
});
}
pub fn set_precision(&self, precision:f32) {
self.data.update(|mut sim| {sim.set_precision(precision); sim});
pub fn set_precision(&self, precision: f32) {
self.data.update(|mut sim| {
sim.set_precision(precision);
sim
});
}
pub fn skip(&self) {
self.data.update(|mut sim| {sim.skip(); sim});
self.data.update(|mut sim| {
sim.skip();
sim
});
}
}
@ -490,7 +553,7 @@ impl<T:Value> SimulationDataCell<T> {
// =================
/// Simulator callback.
pub trait Callback0 = 'static + Fn0;
pub trait Callback0 = 'static + Fn0;
pub trait Callback1<T> = 'static + Fn1<T>;
@ -500,37 +563,42 @@ pub trait Callback1<T> = 'static + Fn1<T>;
// =====================
/// Internal data of `Simulator`.
#[derive(Derivative,Default)]
#[derivative(Debug(bound="T:Copy+Debug"))]
pub struct SimulatorData<T,OnStep,OnStart,OnEnd> {
simulation : SimulationDataCell<T>,
frame_rate : Cell<f32>,
#[derivative(Debug="ignore")]
on_step : OnStep,
#[derivative(Debug="ignore")]
on_start : OnStart,
#[derivative(Debug="ignore")]
on_end : OnEnd,
#[derive(Derivative, Default)]
#[derivative(Debug(bound = "T:Copy+Debug"))]
pub struct SimulatorData<T, OnStep, OnStart, OnEnd> {
simulation: SimulationDataCell<T>,
frame_rate: Cell<f32>,
#[derivative(Debug = "ignore")]
on_step: OnStep,
#[derivative(Debug = "ignore")]
on_start: OnStart,
#[derivative(Debug = "ignore")]
on_end: OnEnd,
}
impl<T,OnStep,OnStart,OnEnd> Deref for SimulatorData<T,OnStep,OnStart,OnEnd> {
impl<T, OnStep, OnStart, OnEnd> Deref for SimulatorData<T, OnStep, OnStart, OnEnd> {
type Target = SimulationDataCell<T>;
fn deref(&self) -> &Self::Target {
&self.simulation
}
}
impl<T,OnStep,OnStart,OnEnd> SimulatorData<T,OnStep,OnStart,OnEnd>
where T:Value, OnStep:Callback1<T>, OnStart:Callback0, OnEnd:Callback1<EndStatus> {
impl<T, OnStep, OnStart, OnEnd> SimulatorData<T, OnStep, OnStart, OnEnd>
where
T: Value,
OnStep: Callback1<T>,
OnStart: Callback0,
OnEnd: Callback1<EndStatus>,
{
/// Constructor.
pub fn new(on_step:OnStep, on_start:OnStart, on_end:OnEnd) -> Self {
pub fn new(on_step: OnStep, on_start: OnStart, on_end: OnEnd) -> Self {
let simulation = SimulationDataCell::new();
let frame_rate = Cell::new(60.0);
Self {simulation,frame_rate,on_step,on_start,on_end}
Self { simulation, frame_rate, on_step, on_start, on_end }
}
/// Proceed with the next simulation step for the given time delta.
pub fn step(&self, delta_seconds:f32) -> bool {
pub fn step(&self, delta_seconds: f32) -> bool {
let is_active = self.simulation.active();
if is_active {
self.simulation.step(delta_seconds);
@ -549,38 +617,43 @@ where T:Value, OnStep:Callback1<T>, OnStart:Callback0, OnEnd:Callback1<EndStatus
// =================
/// Handy alias for `Simulator` with a boxed closure callback.
pub type DynSimulator<T> = Simulator<T,Box<dyn Fn(T)>,(),()>;
pub type DynSimulator<T> = Simulator<T, Box<dyn Fn(T)>, (), ()>;
/// The `SimulationDataCell` with an associated animation loop. The simulation is updated every
/// frame in an efficient way when the simulation finishes, it automatically unregisters the
/// animation loop and registers back only when needed.
#[derive(CloneRef,Derivative)]
#[derivative(Clone(bound=""))]
pub struct Simulator<T,OnStep,OnStart,OnEnd> {
data : Rc<SimulatorData<T,OnStep,OnStart,OnEnd>>,
animation_loop : AnimationLoop<T,OnStep,OnStart,OnEnd> ,
#[derive(CloneRef, Derivative)]
#[derivative(Clone(bound = ""))]
pub struct Simulator<T, OnStep, OnStart, OnEnd> {
data: Rc<SimulatorData<T, OnStep, OnStart, OnEnd>>,
animation_loop: AnimationLoop<T, OnStep, OnStart, OnEnd>,
}
impl<T,OnStep,OnStart,OnEnd> Deref for Simulator<T,OnStep,OnStart,OnEnd> {
type Target = Rc<SimulatorData<T,OnStep,OnStart,OnEnd>>;
impl<T, OnStep, OnStart, OnEnd> Deref for Simulator<T, OnStep, OnStart, OnEnd> {
type Target = Rc<SimulatorData<T, OnStep, OnStart, OnEnd>>;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T,OnStep,OnStart,OnEnd> Simulator<T,OnStep,OnStart,OnEnd>
where T:Value, OnStep:Callback1<T>, OnStart:Callback0, OnEnd:Callback1<EndStatus> {
impl<T, OnStep, OnStart, OnEnd> Simulator<T, OnStep, OnStart, OnEnd>
where
T: Value,
OnStep: Callback1<T>,
OnStart: Callback0,
OnEnd: Callback1<EndStatus>,
{
/// Constructor.
pub fn new(callback:OnStep, on_start:OnStart, on_end:OnEnd) -> Self {
let data = Rc::new(SimulatorData::new(callback,on_start,on_end));
pub fn new(callback: OnStep, on_start: OnStart, on_end: OnEnd) -> Self {
let data = Rc::new(SimulatorData::new(callback, on_start, on_end));
let animation_loop = default();
Self {data,animation_loop} . init()
Self { data, animation_loop }.init()
}
}
impl<T,OnStep,OnStart,OnEnd> Debug for Simulator<T,OnStep,OnStart,OnEnd> {
fn fmt(&self, f:&mut fmt::Formatter<'_>) -> fmt::Result {
write!(f,"Simulator")
impl<T, OnStep, OnStart, OnEnd> Debug for Simulator<T, OnStep, OnStart, OnEnd> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Simulator")
}
}
@ -588,21 +661,26 @@ impl<T,OnStep,OnStart,OnEnd> Debug for Simulator<T,OnStep,OnStart,OnEnd> {
// === Setters ===
#[allow(missing_docs)]
impl<T,OnStep,OnStart,OnEnd> Simulator<T,OnStep,OnStart,OnEnd>
where T:Value, OnStep:Callback1<T>, OnStart:Callback0, OnEnd:Callback1<EndStatus> {
pub fn set_value(&self, value:T) {
impl<T, OnStep, OnStart, OnEnd> Simulator<T, OnStep, OnStart, OnEnd>
where
T: Value,
OnStep: Callback1<T>,
OnStart: Callback0,
OnEnd: Callback1<EndStatus>,
{
pub fn set_value(&self, value: T) {
self.simulation.set_value(value);
self.start();
}
pub fn set_target_value(&self, target_value:T) {
pub fn set_target_value(&self, target_value: T) {
if target_value != self.target_value() {
self.simulation.set_target_value(target_value);
self.start();
}
}
pub fn update_target_value<F:FnOnce(T)->T>(&self, f:F) {
pub fn update_target_value<F: FnOnce(T) -> T>(&self, f: F) {
self.set_target_value(f(self.target_value()))
}
}
@ -610,8 +688,13 @@ where T:Value, OnStep:Callback1<T>, OnStart:Callback0, OnEnd:Callback1<EndStatus
// === Private API ===
impl<T,OnStep,OnStart,OnEnd> Simulator<T,OnStep,OnStart,OnEnd>
where T:Value, OnStep:Callback1<T>, OnStart:Callback0, OnEnd:Callback1<EndStatus> {
impl<T, OnStep, OnStart, OnEnd> Simulator<T, OnStep, OnStart, OnEnd>
where
T: Value,
OnStep: Callback1<T>,
OnStart: Callback0,
OnEnd: Callback1<EndStatus>,
{
fn init(self) -> Self {
self.start();
self
@ -620,9 +703,9 @@ where T:Value, OnStep:Callback1<T>, OnStart:Callback0, OnEnd:Callback1<EndStatus
/// Starts the simulation and attaches it to an animation loop.
fn start(&self) {
if self.animation_loop.get().is_none() {
let frame_rate = self.frame_rate.get();
let step = step(self);
let animation_loop = animation::Loop::new_with_fixed_frame_rate(frame_rate,step);
let frame_rate = self.frame_rate.get();
let step = step(self);
let animation_loop = animation::Loop::new_with_fixed_frame_rate(frame_rate, step);
self.animation_loop.set(Some(animation_loop));
self.on_start.call();
}
@ -650,28 +733,28 @@ where T:Value, OnStep:Callback1<T>, OnStart:Callback0, OnEnd:Callback1<EndStatus
/// A wrapper over animation loop implementation. This type is defined mainly to make Rust type
/// inferencer happy (not infer infinite, recursive types).
#[derive(CloneRef,Derivative)]
#[derivative(Clone(bound=""))]
#[derivative(Default(bound=""))]
#[derive(CloneRef, Derivative)]
#[derivative(Clone(bound = ""))]
#[derivative(Default(bound = ""))]
#[allow(clippy::type_complexity)]
#[allow(missing_debug_implementations)]
pub struct AnimationLoop<T,OnStep,OnStart,OnEnd> {
animation_loop : Rc<CloneCell<Option<FixedFrameRateAnimationStep<T,OnStep,OnStart,OnEnd>>>>,
pub struct AnimationLoop<T, OnStep, OnStart, OnEnd> {
animation_loop: Rc<CloneCell<Option<FixedFrameRateAnimationStep<T, OnStep, OnStart, OnEnd>>>>,
}
#[allow(clippy::type_complexity)]
impl<T,OnStep,OnStart,OnEnd> Deref for AnimationLoop<T,OnStep,OnStart,OnEnd> {
type Target = Rc<CloneCell<Option<FixedFrameRateAnimationStep<T,OnStep,OnStart,OnEnd>>>>;
impl<T, OnStep, OnStart, OnEnd> Deref for AnimationLoop<T, OnStep, OnStart, OnEnd> {
type Target = Rc<CloneCell<Option<FixedFrameRateAnimationStep<T, OnStep, OnStart, OnEnd>>>>;
fn deref(&self) -> &Self::Target {
&self.animation_loop
}
}
impl<T,OnStep,OnStart,OnEnd> AnimationLoop<T,OnStep,OnStart,OnEnd> {
impl<T, OnStep, OnStart, OnEnd> AnimationLoop<T, OnStep, OnStart, OnEnd> {
/// Downgrade to a week reference.
pub fn downgrade(&self) -> WeakAnimationLoop<T,OnStep,OnStart,OnEnd> {
pub fn downgrade(&self) -> WeakAnimationLoop<T, OnStep, OnStart, OnEnd> {
let animation_loop = Rc::downgrade(&self.animation_loop);
WeakAnimationLoop {animation_loop}
WeakAnimationLoop { animation_loop }
}
}
@ -679,14 +762,14 @@ impl<T,OnStep,OnStart,OnEnd> AnimationLoop<T,OnStep,OnStart,OnEnd> {
/// inferencer happy (not infer infinite, recursive types).
#[allow(clippy::type_complexity)]
#[allow(missing_debug_implementations)]
pub struct WeakAnimationLoop<T,OnStep,OnStart,OnEnd> {
animation_loop : Weak<CloneCell<Option<FixedFrameRateAnimationStep<T,OnStep,OnStart,OnEnd>>>>,
pub struct WeakAnimationLoop<T, OnStep, OnStart, OnEnd> {
animation_loop: Weak<CloneCell<Option<FixedFrameRateAnimationStep<T, OnStep, OnStart, OnEnd>>>>,
}
impl<T,OnStep,OnStart,OnEnd> WeakAnimationLoop<T,OnStep,OnStart,OnEnd> {
impl<T, OnStep, OnStart, OnEnd> WeakAnimationLoop<T, OnStep, OnStart, OnEnd> {
/// Upgrade the weak reference.
pub fn upgrade(&self) -> Option<AnimationLoop<T,OnStep,OnStart,OnEnd>> {
self.animation_loop.upgrade().map(|animation_loop| AnimationLoop{animation_loop})
pub fn upgrade(&self) -> Option<AnimationLoop<T, OnStep, OnStart, OnEnd>> {
self.animation_loop.upgrade().map(|animation_loop| AnimationLoop { animation_loop })
}
}
@ -694,18 +777,23 @@ impl<T,OnStep,OnStart,OnEnd> WeakAnimationLoop<T,OnStep,OnStart,OnEnd> {
// === Animation Step ===
/// Alias for `FixedFrameRateLoop` with specified step callback.
pub type FixedFrameRateAnimationStep<T,OnStep,OnStart,OnEnd> =
animation::FixedFrameRateLoop<Step<T,OnStep,OnStart,OnEnd>>;
pub type FixedFrameRateAnimationStep<T, OnStep, OnStart, OnEnd> =
animation::FixedFrameRateLoop<Step<T, OnStep, OnStart, OnEnd>>;
/// Callback for an animation step.
pub type Step<T,OnStep,OnStart,OnEnd> = impl Fn(animation::TimeInfo);
pub type Step<T, OnStep, OnStart, OnEnd> = impl Fn(animation::TimeInfo);
fn step<T,OnStep,OnStart,OnEnd>(simulator:&Simulator<T,OnStep,OnStart,OnEnd>)
-> Step<T,OnStep,OnStart,OnEnd>
where T:Value, OnStep:Callback1<T>, OnStart:Callback0, OnEnd:Callback1<EndStatus> {
let data = simulator.data.clone_ref();
fn step<T, OnStep, OnStart, OnEnd>(
simulator: &Simulator<T, OnStep, OnStart, OnEnd>,
) -> Step<T, OnStep, OnStart, OnEnd>
where
T: Value,
OnStep: Callback1<T>,
OnStart: Callback0,
OnEnd: Callback1<EndStatus>, {
let data = simulator.data.clone_ref();
let animation_loop = simulator.animation_loop.downgrade();
move |time:animation::TimeInfo| {
move |time: animation::TimeInfo| {
let delta_seconds = time.frame / 1000.0;
if !data.step(delta_seconds) {
if let Some(animation_loop) = animation_loop.upgrade() {
@ -723,14 +811,21 @@ where T:Value, OnStep:Callback1<T>, OnStart:Callback0, OnEnd:Callback1<EndStatus
/// Status of the simulation end. It is either normal, which happens when the animation finishes, or
/// forced, which means that it was forced by the user (for example by using the `stop` function).
#[derive(Clone,Copy,Debug,Eq,PartialEq,Hash)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[allow(missing_docs)]
pub enum EndStatus { Normal, Forced }
pub enum EndStatus {
Normal,
Forced,
}
#[allow(missing_docs)]
impl EndStatus {
pub fn is_normal(self) -> bool { self == Self::Normal }
pub fn is_forced(self) -> bool { self == Self::Forced }
pub fn is_normal(self) -> bool {
self == Self::Normal
}
pub fn is_forced(self) -> bool {
self == Self::Forced
}
}
impl Default for EndStatus {
@ -762,7 +857,7 @@ mod tests {
data.set_value(f32::NAN);
data.set_target_value(0.0);
data.step(1.0);
assert_eq!(data.value(),0.0);
assert_eq!(data.value(), 0.0);
assert!(!data.active);
}
}

View File

@ -2,18 +2,18 @@
pub mod args;
pub mod command;
pub mod frp;
pub mod shortcut;
pub mod view;
pub mod frp;
pub use view::View;
use crate::prelude::*;
use crate::control::callback;
use crate::display;
use crate::display::style::theme;
use crate::display::world::World;
use crate::display;
use crate::gui::cursor::Cursor;
use crate::system::web;
use ensogl_system_web::StyleSetter;
@ -26,38 +26,39 @@ use ensogl_system_web::StyleSetter;
/// A top level structure for an application. It combines a view, keyboard shortcut manager, and is
/// intended to also manage layout of visible panes.
#[derive(Debug,Clone,CloneRef)]
#[derive(Debug, Clone, CloneRef)]
#[allow(missing_docs)]
pub struct Application {
pub logger : Logger,
pub cursor : Cursor,
pub display : World,
pub commands : command::Registry,
pub shortcuts : shortcut::Registry,
pub views : view::Registry,
pub themes : theme::Manager,
update_themes_handle : callback::Handle,
pub logger: Logger,
pub cursor: Cursor,
pub display: World,
pub commands: command::Registry,
pub shortcuts: shortcut::Registry,
pub views: view::Registry,
pub themes: theme::Manager,
update_themes_handle: callback::Handle,
}
impl Application {
/// Constructor.
pub fn new(dom:&web_sys::HtmlElement) -> Self {
let logger = Logger::new("Application");
let display = World::new(dom);
let scene = display.scene();
let commands = command::Registry::create(&logger);
let shortcuts = shortcut::Registry::new(&logger,&scene.mouse.frp,&scene.keyboard.frp,&commands);
let views = view::Registry::create(&logger,&display,&commands,&shortcuts);
let themes = theme::Manager::from(&display.scene().style_sheet);
let cursor = Cursor::new(display.scene());
pub fn new(dom: &web_sys::HtmlElement) -> Self {
let logger = Logger::new("Application");
let display = World::new(dom);
let scene = display.scene();
let commands = command::Registry::create(&logger);
let shortcuts =
shortcut::Registry::new(&logger, &scene.mouse.frp, &scene.keyboard.frp, &commands);
let views = view::Registry::create(&logger, &display, &commands, &shortcuts);
let themes = theme::Manager::from(&display.scene().style_sheet);
let cursor = Cursor::new(display.scene());
display.add_child(&cursor);
web::body().set_style_or_panic("cursor","none");
web::body().set_style_or_panic("cursor", "none");
let update_themes_handle = display.on_before_frame(f_!(themes.update()));
Self {logger,cursor,display,commands,shortcuts,views,themes,update_themes_handle}
Self { logger, cursor, display, commands, shortcuts, views, themes, update_themes_handle }
}
/// Create a new instance of a view.
pub fn new_view<T:View>(&self) -> T {
pub fn new_view<T: View>(&self) -> T {
self.views.new_view(self)
}
}

View File

@ -10,12 +10,13 @@ use crate::prelude::*;
// =================
/// Marker trait used to disambiguate overlapping impls of [`ArgReader`].
#[marker] pub trait ArgMarker {}
#[marker]
pub trait ArgMarker {}
/// Trait used to convert provided string arguments to the desired type.
#[allow(missing_docs)]
pub trait ArgReader : Sized {
fn read_arg(str:String) -> Option<Self>;
pub trait ArgReader: Sized {
fn read_arg(str: String) -> Option<Self>;
}
@ -23,26 +24,29 @@ pub trait ArgReader : Sized {
/// Helper trait used to disambiguate overlapping impls of [`ArgReader`].
#[allow(missing_docs)]
pub trait ArgReaderFromString : Sized {
fn read_arg_from_string(str:String) -> Option<Self>;
pub trait ArgReaderFromString: Sized {
fn read_arg_from_string(str: String) -> Option<Self>;
}
impl<T> ArgReaderFromString for T
where String:TryInto<T> {
fn read_arg_from_string(str:String) -> Option<Self> {
where String: TryInto<T>
{
fn read_arg_from_string(str: String) -> Option<Self> {
str.try_into().ok()
}
}
impl<T> ArgReaderFromString for T {
default fn read_arg_from_string(_:String) -> Option<Self> {
default fn read_arg_from_string(_: String) -> Option<Self> {
unreachable!()
}
}
impl<T> ArgMarker for T where T : TryFrom<String> {}
impl<T> ArgReader for T where T : ArgMarker {
default fn read_arg(str:String) -> Option<Self> {
impl<T> ArgMarker for T where T: TryFrom<String> {}
impl<T> ArgReader for T
where T: ArgMarker
{
default fn read_arg(str: String) -> Option<Self> {
ArgReaderFromString::read_arg_from_string(str)
}
}
@ -52,17 +56,17 @@ impl<T> ArgReader for T where T : ArgMarker {
impl ArgMarker for bool {}
impl ArgReader for bool {
fn read_arg(str:String) -> Option<Self> {
fn read_arg(str: String) -> Option<Self> {
match &str[..] {
"true" => Some(true),
"false" => Some(false),
"ok" => Some(true),
"fail" => Some(false),
"enabled" => Some(true),
"true" => Some(true),
"false" => Some(false),
"ok" => Some(true),
"fail" => Some(false),
"enabled" => Some(true),
"disabled" => Some(false),
"yes" => Some(true),
"no" => Some(false),
_ => None,
"yes" => Some(true),
"no" => Some(false),
_ => None,
}
}
}

View File

@ -1,8 +1,8 @@
//! Definition of commands, labeled FPR endpoints useful when implementing actions which can be
//! altered at runtime, like a keyboard shortcut management.
use crate::prelude::*;
use crate::frp;
use crate::prelude::*;
use crate::application::shortcut;
use crate::application::shortcut::Shortcut;
@ -17,15 +17,15 @@ use crate::application::Application;
/// All view components should deref to their FRP definitions, which should implement
/// the `CommandApi`. Please note that it is automatically derived if you use the
/// `define_endpoints!` macro.
pub trait DerefToCommandApi = Deref where <Self as Deref>::Target : CommandApi;
pub trait DerefToCommandApi = Deref where <Self as Deref>::Target: CommandApi;
/// A visual component of an application.
pub trait View : FrpNetworkProvider + DerefToCommandApi {
pub trait View: FrpNetworkProvider + DerefToCommandApi {
/// Identifier of the command provider class.
fn label() -> &'static str;
/// Constructor.
fn new(app:&Application) -> Self;
fn new(app: &Application) -> Self;
/// Application reference.
fn app(&self) -> &Application;
@ -36,32 +36,39 @@ pub trait View : FrpNetworkProvider + DerefToCommandApi {
}
/// Add a new shortcut targeting the self object.
fn self_shortcut
( action_type : shortcut::ActionType
, pattern : impl Into<String>
, command : impl Into<shortcut::Command>
fn self_shortcut(
action_type: shortcut::ActionType,
pattern: impl Into<String>,
command: impl Into<shortcut::Command>,
) -> Shortcut {
Shortcut::new(shortcut::Rule::new(action_type,pattern),Self::label(),command)
Shortcut::new(shortcut::Rule::new(action_type, pattern), Self::label(), command)
}
/// Add a new shortcut targeting the self object.
fn self_shortcut_when
( action_type : shortcut::ActionType
, pattern : impl Into<String>
, command : impl Into<shortcut::Command>
, condition : impl Into<shortcut::Condition>
fn self_shortcut_when(
action_type: shortcut::ActionType,
pattern: impl Into<String>,
command: impl Into<shortcut::Command>,
condition: impl Into<shortcut::Condition>,
) -> Shortcut {
Shortcut::new_when(shortcut::Rule::new(action_type,pattern),Self::label(),command,condition)
Shortcut::new_when(
shortcut::Rule::new(action_type, pattern),
Self::label(),
command,
condition,
)
}
/// Disable the command in this component instance.
fn disable_command(&self, name:impl AsRef<str>) where Self:Sized {
self.app().commands.disable_command(self,name)
fn disable_command(&self, name: impl AsRef<str>)
where Self: Sized {
self.app().commands.disable_command(self, name)
}
/// Enable the command in this component instance.
fn enable_command(&self, name:impl AsRef<str>) where Self:Sized {
self.app().commands.enable_command(self,name)
fn enable_command(&self, name: impl AsRef<str>)
where Self: Sized {
self.app().commands.enable_command(self, name)
}
}
@ -82,8 +89,8 @@ pub trait FrpNetworkProvider {
#[derive(Debug)]
#[allow(missing_docs)]
pub struct Command {
pub frp : frp::Any,
pub enabled : bool,
pub frp: frp::Any,
pub enabled: bool,
}
impl Deref for Command {
@ -95,9 +102,9 @@ impl Deref for Command {
impl Command {
/// Constructor.
pub fn new(frp:frp::Any<()>) -> Self {
pub fn new(frp: frp::Any<()>) -> Self {
let enabled = true;
Self {frp,enabled}
Self { frp, enabled }
}
}
@ -110,9 +117,13 @@ impl Command {
/// The API for a command provider. This trait should not be provided manually.
/// Use the `define_endpoints!` macro to auto-derive it.
#[allow(missing_docs)]
pub trait CommandApi : Sized {
fn command_api(&self) -> Rc<RefCell<HashMap<String,Command>>> { default() }
fn status_api(&self) -> Rc<RefCell<HashMap<String,frp::Sampler<bool>>>> { default() }
pub trait CommandApi: Sized {
fn command_api(&self) -> Rc<RefCell<HashMap<String, Command>>> {
default()
}
fn status_api(&self) -> Rc<RefCell<HashMap<String, frp::Sampler<bool>>>> {
default()
}
}
@ -123,12 +134,12 @@ pub trait CommandApi : Sized {
/// Generic interface to an instance of a component. It contains bindings to all FRP endpoints
/// defined by the component (often by using the `define_endpoints!` macro).
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
#[allow(missing_docs)]
pub struct ProviderInstance {
pub network : frp::WeakNetwork,
pub command_map : Rc<RefCell<HashMap<String,Command>>>,
pub status_map : Rc<RefCell<HashMap<String,frp::Sampler<bool>>>>,
pub network: frp::WeakNetwork,
pub command_map: Rc<RefCell<HashMap<String, Command>>>,
pub status_map: Rc<RefCell<HashMap<String, frp::Sampler<bool>>>>,
}
impl ProviderInstance {
@ -151,80 +162,82 @@ impl ProviderInstance {
/// A command registry. Allows registering command providers (gui components) and corresponding
/// `ProviderInstance`s.
#[derive(Debug,Clone,CloneRef)]
#[derive(Debug, Clone, CloneRef)]
#[allow(missing_docs)]
pub struct Registry {
pub logger : Logger,
pub name_map : Rc<RefCell<HashMap<String,Vec<ProviderInstance>>>>,
pub id_map : Rc<RefCell<HashMap<frp::NetworkId,ProviderInstance>>>,
pub logger: Logger,
pub name_map: Rc<RefCell<HashMap<String, Vec<ProviderInstance>>>>,
pub id_map: Rc<RefCell<HashMap<frp::NetworkId, ProviderInstance>>>,
}
impl Registry {
/// Constructor.
pub fn create(logger:impl AnyLogger) -> Self {
let logger = Logger::new_sub(logger,"views");
pub fn create(logger: impl AnyLogger) -> Self {
let logger = Logger::new_sub(logger, "views");
let name_map = default();
let id_map = default();
Self {logger,name_map,id_map}
let id_map = default();
Self { logger, name_map, id_map }
}
/// Registers a gui component as a command provider.
pub fn register<V:View>(&self) {
let label = V::label();
pub fn register<V: View>(&self) {
let label = V::label();
let exists = self.name_map.borrow().get(label).is_some();
if exists {
warning!(&self.logger, "The view '{label}' was already registered.")
} else {
self.name_map.borrow_mut().insert(label.into(),default());
self.name_map.borrow_mut().insert(label.into(), default());
}
}
/// Registers the command `ProviderInstance`.
pub fn register_instance<T:View>(&self, target:&T) {
let label = T::label();
let network = T::network(target).downgrade();
pub fn register_instance<T: View>(&self, target: &T) {
let label = T::label();
let network = T::network(target).downgrade();
let command_map = target.deref().command_api();
let status_map = target.deref().status_api();
let instance = ProviderInstance {network,command_map,status_map};
let status_map = target.deref().status_api();
let instance = ProviderInstance { network, command_map, status_map };
let was_registered = self.name_map.borrow().get(label).is_some();
if !was_registered {
self.register::<T>();
warning!(&self.logger,
warning!(
&self.logger,
"The command provider '{label}' was created but never registered. You should \
always register available command providers as soon as possible to spread the \
information about their API.");
information about their API."
);
};
let id = instance.id();
self.name_map.borrow_mut().get_mut(label).unwrap().push(instance.clone_ref());
self.id_map.borrow_mut().insert(id,instance);
self.id_map.borrow_mut().insert(id, instance);
}
/// Queries the command map by command name and applies the provided function to the result.
/// Emits warnings in case the command could not be found.
fn with_command_mut<T:View>
( &self
, target : &T
, name : impl AsRef<str>
, f : impl Fn(&mut Command)
fn with_command_mut<T: View>(
&self,
target: &T,
name: impl AsRef<str>,
f: impl Fn(&mut Command),
) {
let name = name.as_ref();
let id = T::network(target).id();
let id = T::network(target).id();
match self.id_map.borrow_mut().get(&id) {
None => warning!(&self.logger,"The provided component ID is invalid {id}."),
None => warning!(&self.logger, "The provided component ID is invalid {id}."),
Some(instance) => match instance.command_map.borrow_mut().get_mut(name) {
None => warning!(&self.logger,"The command name {name} is invalid."),
Some(command) => f(command)
}
None => warning!(&self.logger, "The command name {name} is invalid."),
Some(command) => f(command),
},
}
}
/// Disables the command for the provided component instance.
fn disable_command<T:View>(&self, instance:&T, name:impl AsRef<str>) {
self.with_command_mut(instance,name,|command| command.enabled = false)
fn disable_command<T: View>(&self, instance: &T, name: impl AsRef<str>) {
self.with_command_mut(instance, name, |command| command.enabled = false)
}
/// Enables the command for the provided component instance.
fn enable_command<T:View>(&self, instance:&T, name:impl AsRef<str>) {
self.with_command_mut(instance,name,|command| command.enabled = true)
fn enable_command<T: View>(&self, instance: &T, name: impl AsRef<str>) {
self.with_command_mut(instance, name, |command| command.enabled = true)
}
}

View File

@ -414,18 +414,18 @@ macro_rules! define_endpoints {
#[macro_export]
macro_rules! build_status_map {
($map:ident $field:ident (bool) $frp:expr) => {
$map.insert(stringify!($field).into(),$frp.clone_ref());
$map.insert(stringify!($field).into(), $frp.clone_ref());
};
($($ts:tt)*) => {}
($($ts:tt)*) => {};
}
/// Internal helper of `define_endpoints` macro.
#[macro_export]
macro_rules! build_command_map {
($map:ident $field:ident () $frp:expr) => {
$map.insert(stringify!($field).into(),Command::new($frp.clone_ref()));
$map.insert(stringify!($field).into(), Command::new($frp.clone_ref()));
};
($($ts:tt)*) => {}
($($ts:tt)*) => {};
}
/// Defines a method which is an alias to FRP emit method. Used internally by the `define_endpoints`
@ -441,71 +441,67 @@ macro_rules! define_endpoints_emit_alias {
($field:ident ($t1:ty,$t2:ty)) => {
#[allow(missing_docs)]
pub fn $field
( &self
, t1:impl IntoParam<$t1>
, t2:impl IntoParam<$t2>
) {
pub fn $field(&self, t1: impl IntoParam<$t1>, t2: impl IntoParam<$t2>) {
let t1 = t1.into_param();
let t2 = t2.into_param();
self.$field.emit((t1,t2));
self.$field.emit((t1, t2));
}
};
($field:ident ($t1:ty,$t2:ty,$t3:ty)) => {
#[allow(missing_docs)]
pub fn $field
( &self
, t1:impl IntoParam<$t1>
, t2:impl IntoParam<$t2>
, t3:impl IntoParam<$t3>
pub fn $field(
&self,
t1: impl IntoParam<$t1>,
t2: impl IntoParam<$t2>,
t3: impl IntoParam<$t3>,
) {
let t1 = t1.into_param();
let t2 = t2.into_param();
let t3 = t3.into_param();
self.$field.emit((t1,t2,t3));
self.$field.emit((t1, t2, t3));
}
};
($field:ident ($t1:ty,$t2:ty,$t3:ty,$t4:ty)) => {
#[allow(missing_docs)]
pub fn $field
( &self
, t1:impl IntoParam<$t1>
, t2:impl IntoParam<$t2>
, t3:impl IntoParam<$t3>
, t4:impl IntoParam<$t4>
pub fn $field(
&self,
t1: impl IntoParam<$t1>,
t2: impl IntoParam<$t2>,
t3: impl IntoParam<$t3>,
t4: impl IntoParam<$t4>,
) {
let t1 = t1.into_param();
let t2 = t2.into_param();
let t3 = t3.into_param();
let t4 = t4.into_param();
self.$field.emit((t1,t2,t3,t4));
self.$field.emit((t1, t2, t3, t4));
}
};
($field:ident ($t1:ty,$t2:ty,$t3:ty,$t4:ty,$t5:ty)) => {
#[allow(missing_docs)]
pub fn $field
( &self
, t1:impl IntoParam<$t1>
, t2:impl IntoParam<$t2>
, t3:impl IntoParam<$t3>
, t4:impl IntoParam<$t4>
, t5:impl IntoParam<$t5>
pub fn $field(
&self,
t1: impl IntoParam<$t1>,
t2: impl IntoParam<$t2>,
t3: impl IntoParam<$t3>,
t4: impl IntoParam<$t4>,
t5: impl IntoParam<$t5>,
) {
let t1 = t1.into_param();
let t2 = t2.into_param();
let t3 = t3.into_param();
let t4 = t4.into_param();
let t5 = t5.into_param();
self.$field.emit((t1,t2,t3,t4,t5));
self.$field.emit((t1, t2, t3, t4, t5));
}
};
($field:ident $t1:ty) => {
#[allow(missing_docs)]
pub fn $field(&self,t1:impl IntoParam<$t1>) {
pub fn $field(&self, t1: impl IntoParam<$t1>) {
self.$field.emit(t1);
}
};

View File

@ -4,9 +4,9 @@ use crate::prelude::*;
use super::command;
use crate::frp;
use crate::frp::io::keyboard;
use crate::frp::io::mouse::Mouse;
use crate::frp;
use enso_shortcuts as shortcuts;
use enso_shortcuts::traits::*;
@ -21,19 +21,19 @@ pub use shortcuts::ActionType;
/// Shortcut action rule, a combination of `ActionType`, like `Press` and a pattern, like
/// "ctrl shift s".
#[derive(Clone,Debug,Eq,PartialEq,Hash)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[allow(missing_docs)]
pub struct Rule {
pub tp : ActionType,
pub pattern : String,
pub tp: ActionType,
pub pattern: String,
}
impl Rule {
/// Constructor.
pub fn new(tp:impl Into<ActionType>, pattern:impl Into<String>) -> Self {
let tp = tp.into();
pub fn new(tp: impl Into<ActionType>, pattern: impl Into<String>) -> Self {
let tp = tp.into();
let pattern = pattern.into();
Self {tp,pattern}
Self { tp, pattern }
}
}
@ -44,14 +44,14 @@ impl Rule {
// ===============
/// A command, textual label of action that should be evaluated in the target component.
#[derive(Clone,Debug,Eq,From,Hash,Into,PartialEq,Shrinkwrap)]
#[derive(Clone, Debug, Eq, From, Hash, Into, PartialEq, Shrinkwrap)]
pub struct Command {
name : String,
name: String,
}
impl From<&str> for Command {
fn from(s:&str) -> Self {
Self {name:s.into()}
fn from(s: &str) -> Self {
Self { name: s.into() }
}
}
@ -62,43 +62,43 @@ impl From<&str> for Command {
// =================
/// Condition expression.
#[derive(Clone,Debug,Eq,PartialEq,Hash)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[allow(missing_docs)]
pub enum Condition {
Always,
Never,
When (String),
Not (Box<Condition>),
Or (Box<Condition>, Box<Condition>),
And (Box<Condition>, Box<Condition>),
When(String),
Not(Box<Condition>),
Or(Box<Condition>, Box<Condition>),
And(Box<Condition>, Box<Condition>),
}
impl Condition {
fn when(t:impl Into<String>) -> Self {
fn when(t: impl Into<String>) -> Self {
Self::When(t.into())
}
fn not(a:Self) -> Self {
fn not(a: Self) -> Self {
Self::Not(Box::new(a))
}
fn and(a:Self, b:Self) -> Self {
Self::And(Box::new(a),Box::new(b))
fn and(a: Self, b: Self) -> Self {
Self::And(Box::new(a), Box::new(b))
}
fn or(a:Self, b:Self) -> Self {
Self::Or(Box::new(a),Box::new(b))
fn or(a: Self, b: Self) -> Self {
Self::Or(Box::new(a), Box::new(b))
}
/// Split the input on the provided `separator`, process each chunk with `f`, and fold results
/// using the `cons`.
fn split_parse
( input : &str
, separator : char
, cons : impl Fn(Self,Self) -> Self
, f : impl Fn(&str) -> Self
fn split_parse(
input: &str,
separator: char,
cons: impl Fn(Self, Self) -> Self,
f: impl Fn(&str) -> Self,
) -> Self {
input.split(separator).map(|t|t.trim()).map(f).fold1(cons).unwrap_or(Self::Never)
input.split(separator).map(|t| t.trim()).map(f).fold1(cons).unwrap_or(Self::Never)
}
/// Parses the provided input expression. The currently recognizable symbols are (sorted by
@ -106,24 +106,26 @@ impl Condition {
/// For example, it parses the following expression: "a & b | !c". Parentheses are not supported
/// yet.
#[allow(clippy::redundant_closure)] // Rust TC does not agree.
fn parse(s:impl AsRef<str>) -> Self {
fn parse(s: impl AsRef<str>) -> Self {
let s = s.as_ref().trim();
if s.is_empty() { Self::Always } else {
Self::split_parse(s,'|',Self::or,|s|
Self::split_parse(s,'&',Self::and,|s|{
if s.is_empty() {
Self::Always
} else {
Self::split_parse(s, '|', Self::or, |s| {
Self::split_parse(s, '&', Self::and, |s| {
if let Some(expr) = s.strip_prefix('!') {
Self::not(Self::when(expr.trim()))
} else {
Self::when(s)
}
})
)
})
}
}
}
impl From<&str> for Condition {
fn from(s:&str) -> Self {
fn from(s: &str) -> Self {
Self::parse(s)
}
}
@ -137,26 +139,29 @@ impl From<&str> for Condition {
/// A shortcut action. Consist of target identifier (like "TextEditor"), a `Command` that will be
/// evaluated on the target, and a `Condition` which needs to be true in order for the command
/// to be executed.
#[derive(Clone,Debug,Eq,PartialEq,Hash)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Action {
target : String,
command : Command,
condition : Condition,
target: String,
command: Command,
condition: Condition,
}
impl Action {
/// Constructor. Version without condition checker.
pub fn new(target:impl Into<String>, command:impl Into<Command>) -> Self {
Self::new_when(target,command,Condition::Always)
pub fn new(target: impl Into<String>, command: impl Into<Command>) -> Self {
Self::new_when(target, command, Condition::Always)
}
/// Constructor.
pub fn new_when
(target:impl Into<String>, command:impl Into<Command>, condition:impl Into<Condition>) -> Self {
let target = target.into();
pub fn new_when(
target: impl Into<String>,
command: impl Into<Command>,
condition: impl Into<Condition>,
) -> Self {
let target = target.into();
let condition = condition.into();
let command = command.into();
Self {target,command,condition}
let command = command.into();
Self { target, command, condition }
}
}
@ -167,35 +172,35 @@ impl Action {
// ================
/// A keyboard shortcut, an `Rule` associated with a `Action`.
#[derive(Clone,Debug,Eq,PartialEq,Hash,Shrinkwrap)]
#[derive(Clone, Debug, Eq, PartialEq, Hash, Shrinkwrap)]
pub struct Shortcut {
#[shrinkwrap(main_field)]
action : Action,
rule : Rule,
action: Action,
rule: Rule,
}
impl Shortcut {
/// Constructor. Version without condition checker.
pub fn new
( rule : impl Into<Rule>
, target : impl Into<String>
, command : impl Into<Command>
pub fn new(
rule: impl Into<Rule>,
target: impl Into<String>,
command: impl Into<Command>,
) -> Self {
let action = Action::new(target,command);
let rule = rule.into();
Self {action,rule}
let action = Action::new(target, command);
let rule = rule.into();
Self { action, rule }
}
/// Constructor.
pub fn new_when
( rule : impl Into<Rule>
, target : impl Into<String>
, command : impl Into<Command>
, condition : impl Into<Condition>
pub fn new_when(
rule: impl Into<Rule>,
target: impl Into<String>,
command: impl Into<Command>,
condition: impl Into<Condition>,
) -> Self {
let action = Action::new_when(target,command,condition);
let rule = rule.into();
Self {action,rule}
let action = Action::new_when(target, command, condition);
let rule = rule.into();
Self { action, rule }
}
}
@ -214,20 +219,20 @@ impl Shortcut {
/// ## Implementation Notes
/// There should be a layer for user shortcuts which will remember handles permanently until a
/// shortcut is unregistered.
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
pub struct Registry {
model : RegistryModel,
network : frp::Network,
model: RegistryModel,
network: frp::Network,
}
/// Internal representation of `Registry`.
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
pub struct RegistryModel {
logger : Logger,
keyboard : keyboard::Keyboard,
mouse : Mouse,
command_registry : command::Registry,
shortcuts_registry : shortcuts::HashSetRegistry<Shortcut>,
logger: Logger,
keyboard: keyboard::Keyboard,
mouse: Mouse,
command_registry: command::Registry,
shortcuts_registry: shortcuts::HashSetRegistry<Shortcut>,
}
impl Deref for Registry {
@ -239,10 +244,13 @@ impl Deref for Registry {
impl Registry {
/// Constructor.
pub fn new
(logger:&Logger, mouse:&Mouse, keyboard:&keyboard::Keyboard, cmd_registry:&command::Registry)
-> Self {
let model = RegistryModel::new(logger,mouse,keyboard,cmd_registry);
pub fn new(
logger: &Logger,
mouse: &Mouse,
keyboard: &keyboard::Keyboard,
cmd_registry: &command::Registry,
) -> Self {
let model = RegistryModel::new(logger, mouse, keyboard, cmd_registry);
let mouse = &model.mouse;
frp::new_network! { network
@ -253,27 +261,27 @@ impl Registry {
event <- any(kb_down,kb_up,mouse_down,mouse_up);
eval event ((m) model.process_rules(m));
}
Self {model,network}
Self { model, network }
}
}
impl RegistryModel {
/// Constructor.
pub fn new
( logger : impl AnyLogger
, mouse : &Mouse
, keyboard : &keyboard::Keyboard
, command_registry : &command::Registry
pub fn new(
logger: impl AnyLogger,
mouse: &Mouse,
keyboard: &keyboard::Keyboard,
command_registry: &command::Registry,
) -> Self {
let logger = Logger::new_sub(logger,"ShortcutRegistry");
let keyboard = keyboard.clone_ref();
let mouse = mouse.clone_ref();
let command_registry = command_registry.clone_ref();
let logger = Logger::new_sub(logger, "ShortcutRegistry");
let keyboard = keyboard.clone_ref();
let mouse = mouse.clone_ref();
let command_registry = command_registry.clone_ref();
let shortcuts_registry = default();
Self {logger,keyboard,mouse,command_registry,shortcuts_registry}
Self { logger, keyboard, mouse, command_registry, shortcuts_registry }
}
fn process_rules(&self, rules:&[Shortcut]) {
fn process_rules(&self, rules: &[Shortcut]) {
let mut targets = Vec::new();
{
let borrowed_command_map = self.command_registry.name_map.borrow();
@ -281,12 +289,17 @@ impl RegistryModel {
let target = &rule.action.target;
borrowed_command_map.get(target).for_each(|instances| {
for instance in instances {
if Self::condition_checker(&rule.condition,&instance.status_map) {
if Self::condition_checker(&rule.condition, &instance.status_map) {
let command_name = &rule.command.name;
match instance.command_map.borrow().get(command_name){
Some(cmd) => if cmd.enabled { targets.push(cmd.frp.clone_ref()) },
None => warning!(&self.logger,
"Command {command_name} was not found on {target}."),
match instance.command_map.borrow().get(command_name) {
Some(cmd) =>
if cmd.enabled {
targets.push(cmd.frp.clone_ref())
},
None => warning!(
&self.logger,
"Command {command_name} was not found on {target}."
),
}
}
}
@ -298,23 +311,29 @@ impl RegistryModel {
}
}
fn condition_checker
(condition:&Condition, status:&Rc<RefCell<HashMap<String,frp::Sampler<bool>>>>) -> bool {
fn condition_checker(
condition: &Condition,
status: &Rc<RefCell<HashMap<String, frp::Sampler<bool>>>>,
) -> bool {
use Condition::*;
match condition {
Always => true,
Never => false,
Always => true,
Never => false,
When(name) => status.borrow().get(name).map(|t| t.value()).unwrap_or(false),
Not(a) => !Self::condition_checker(a,status),
Or(a,b) => Self::condition_checker(a,status) || Self::condition_checker(b,status),
And(a,b) => Self::condition_checker(a,status) && Self::condition_checker(b,status),
Not(a) => !Self::condition_checker(a, status),
Or(a, b) => Self::condition_checker(a, status) || Self::condition_checker(b, status),
And(a, b) => Self::condition_checker(a, status) && Self::condition_checker(b, status),
}
}
}
impl Add<Shortcut> for &Registry {
type Output = ();
fn add(self, shortcut:Shortcut) {
self.model.shortcuts_registry.add(shortcut.rule.tp,&shortcut.rule.pattern,shortcut.clone());
fn add(self, shortcut: Shortcut) {
self.model.shortcuts_registry.add(
shortcut.rule.tp,
&shortcut.rule.pattern,
shortcut.clone(),
);
}
}

View File

@ -2,10 +2,10 @@
use crate::prelude::*;
use crate::display::world::World;
use super::command;
use super::shortcut;
use super::Application;
use crate::display::world::World;
pub use command::View;
@ -17,34 +17,34 @@ pub use command::View;
/// View registry. Please note that all view definitions should be registered here as soon as
/// possible in order to enable their default shortcuts and spread the information about their API.
#[derive(Debug,Clone,CloneRef)]
#[derive(Debug, Clone, CloneRef)]
#[allow(missing_docs)]
pub struct Registry {
pub logger : Logger,
pub display : World,
pub command_registry : command::Registry,
pub shortcut_registry : shortcut::Registry,
pub definitions : Rc<RefCell<HashSet<String>>>,
pub logger: Logger,
pub display: World,
pub command_registry: command::Registry,
pub shortcut_registry: shortcut::Registry,
pub definitions: Rc<RefCell<HashSet<String>>>,
}
impl Registry {
/// Constructor.
pub fn create
( logger : impl AnyLogger
, display : &World
, command_registry : &command::Registry
, shortcut_registry : &shortcut::Registry
pub fn create(
logger: impl AnyLogger,
display: &World,
command_registry: &command::Registry,
shortcut_registry: &shortcut::Registry,
) -> Self {
let logger = Logger::new_sub(logger,"view_registry");
let display = display.clone_ref();
let command_registry = command_registry.clone_ref();
let logger = Logger::new_sub(logger, "view_registry");
let display = display.clone_ref();
let command_registry = command_registry.clone_ref();
let shortcut_registry = shortcut_registry.clone_ref();
let definitions = default();
Self {logger,display,command_registry,shortcut_registry,definitions}
let definitions = default();
Self { logger, display, command_registry, shortcut_registry, definitions }
}
/// View registration.
pub fn register<V:View>(&self) {
pub fn register<V: View>(&self) {
let label = V::label().into();
for shortcut in V::default_shortcuts() {
self.shortcut_registry.add(shortcut)
@ -54,14 +54,16 @@ impl Registry {
}
/// New view constructor.
pub fn new_view<V:View>(&self, app:&Application) -> V {
let label = V::label();
pub fn new_view<V: View>(&self, app: &Application) -> V {
let label = V::label();
let was_registered = self.definitions.borrow().get(label).is_some();
if !was_registered {
warning!(&self.logger,
warning!(
&self.logger,
"The view '{label}' was created but never registered, performing automatic \
registration. You should always register available views as soon as possible to \
enable their default shortcuts and spread the information about their API.");
enable their default shortcuts and spread the information about their API."
);
self.register::<V>();
}
let view = V::new(app);

View File

@ -9,12 +9,12 @@ use crate::system::web;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use wasm_bindgen::prelude::Closure;
pub use event::*;
pub use crate::frp::io::mouse::*;
pub use event::*;
@ -25,21 +25,21 @@ pub use crate::frp::io::mouse::*;
// TODO: Consider merging this implementation with crate::control::callback::* ones.
/// Shared event dispatcher.
#[derive(Debug,CloneRef,Derivative)]
#[derivative(Clone(bound=""))]
#[derivative(Default(bound=""))]
#[derive(Debug, CloneRef, Derivative)]
#[derivative(Clone(bound = ""))]
#[derivative(Default(bound = ""))]
pub struct EventDispatcher<T> {
rc: Rc<RefCell<callback::Registry1<T>>>
rc: Rc<RefCell<callback::Registry1<T>>>,
}
impl<T> EventDispatcher<T> {
/// Adds a new callback.
pub fn add<F:FnMut(&T)+'static>(&self, f:F) -> callback::Handle {
pub fn add<F: FnMut(&T) + 'static>(&self, f: F) -> callback::Handle {
self.rc.borrow_mut().add(f)
}
/// Dispatches event to all callbacks.
pub fn dispatch(&self, t:&T) {
pub fn dispatch(&self, t: &T) {
self.rc.borrow_mut().run_all(t);
}
}
@ -52,12 +52,12 @@ impl<T> EventDispatcher<T> {
/// An utility which registers JavaScript handlers for mouse events and translates them to Rust
/// handlers. It is a top level mouse registry hub.
#[derive(Clone,CloneRef,Debug,Shrinkwrap)]
#[derive(Clone, CloneRef, Debug, Shrinkwrap)]
pub struct MouseManager {
#[shrinkwrap(main_field)]
dispatchers : MouseManagerDispatchers,
closures : Rc<MouseManagerClosures>,
dom : web::dom::WithKnownShape<web::EventTarget>
dispatchers: MouseManagerDispatchers,
closures: Rc<MouseManagerClosures>,
dom: web::dom::WithKnownShape<web::EventTarget>,
}
/// A JavaScript callback closure for any mouse event.
@ -155,29 +155,28 @@ define_bindings! {
/// A handles of callbacks emitting events on bound FRP graph. See `callback::Handle`.
#[derive(Debug)]
pub struct MouseFrpCallbackHandles {
on_move : callback::Handle,
on_down : callback::Handle,
on_up : callback::Handle,
on_wheel : callback::Handle
on_move: callback::Handle,
on_down: callback::Handle,
on_up: callback::Handle,
on_wheel: callback::Handle,
}
// FIXME: This is obsolete. Use mouse bindings from scene instead.
/// Bind FRP graph to MouseManager.
pub fn bind_frp_to_mouse(frp:&Mouse, mouse_manager:&MouseManager)
-> MouseFrpCallbackHandles {
pub fn bind_frp_to_mouse(frp: &Mouse, mouse_manager: &MouseManager) -> MouseFrpCallbackHandles {
let dom_shape = mouse_manager.dom.clone_ref().shape();
let on_move = enclose!((frp.position => frp) move |e:&OnMove| {
let position = Vector2(e.client_x() as f32,e.client_y() as f32);
let position = position - Vector2(dom_shape.width,dom_shape.height) / 2.0;
frp.emit(position);
});
let on_down = enclose!((frp.down => frp) move |_:&OnDown | frp.emit(Button0));
let on_up = enclose!((frp.up => frp) move |_:&OnUp | frp.emit(Button0));
let on_down = enclose!((frp.down => frp) move |_:&OnDown | frp.emit(Button0));
let on_up = enclose!((frp.up => frp) move |_:&OnUp | frp.emit(Button0));
let on_wheel = enclose!((frp.wheel => frp) move |_:&OnWheel| frp.emit(()));
MouseFrpCallbackHandles {
on_move : mouse_manager.on_move.add(on_move),
on_down : mouse_manager.on_down.add(on_down),
on_up : mouse_manager.on_up.add(on_up),
on_wheel : mouse_manager.on_wheel.add(on_wheel)
on_move: mouse_manager.on_move.add(on_move),
on_down: mouse_manager.on_down.add(on_down),
on_up: mouse_manager.on_up.add(on_up),
on_wheel: mouse_manager.on_wheel.add(on_wheel),
}
}

View File

@ -2,8 +2,8 @@
use crate::prelude::*;
use enso_frp::io::mouse;
use crate::system::web::dom::Shape;
use enso_frp::io::mouse;
use wasm_bindgen::JsCast;

View File

@ -15,15 +15,15 @@
//! make them more pleasant to work with, however, the equations you will fnd will probably work on
//! different value ranges. Read documentation for each color space very carefully.
pub mod animation;
pub mod component;
pub mod data;
pub mod gradient;
pub mod mix;
pub mod space;
pub mod animation;
pub use self::data::*;
pub use animation::Animation;
pub use component::*;
pub use mix::mix;
pub use self::data::*;
pub use space::*;

View File

@ -3,8 +3,8 @@
//! discontinuities of the polar coordinates of Lcha (i.e., a transition from hue 1 to 359 would go
//! through all hues instead of taking the shorter trip "backwards").
use crate::prelude::*;
use super::*;
use crate::prelude::*;
use enso_frp as frp;
@ -28,14 +28,14 @@ crate::define_endpoints! {
}
/// The `Animation` provides color better animations for colors than the raw
/// `component::DEPRECATED_Animation<_>`, as it allows controlling the alpha channel separately which is
/// important for nice fade outs.
#[derive(Clone,CloneRef,Debug)]
/// `component::DEPRECATED_Animation<_>`, as it allows controlling the alpha channel separately
/// which is important for nice fade outs.
#[derive(Clone, CloneRef, Debug)]
#[allow(missing_docs)]
pub struct Animation {
frp : FrpEndpoints,
color_anim : crate::Animation<Lch>,
alpha_anim : crate::Animation<f32>,
frp: FrpEndpoints,
color_anim: crate::Animation<Lch>,
alpha_anim: crate::Animation<f32>,
}
impl Deref for Animation {
@ -47,14 +47,14 @@ impl Deref for Animation {
impl Animation {
/// Constructor.
pub fn new(network:&frp::Network) -> Self {
let frp = Frp::extend(network);
pub fn new(network: &frp::Network) -> Self {
let frp = Frp::extend(network);
let color_anim = crate::Animation::new_non_init(network);
let alpha_anim = crate::Animation::new_non_init(network);
Self{frp,color_anim,alpha_anim}.init(network)
Self { frp, color_anim, alpha_anim }.init(network)
}
fn init(self, network:&frp::Network) -> Self {
fn init(self, network: &frp::Network) -> Self {
frp::extend! { network
color_of_target <- self.frp.target.map(|t|t.opaque);
alpha_of_target <- self.frp.target.map(|t|t.alpha);

View File

@ -14,22 +14,22 @@ use nalgebra::Vector4;
/// Wrapper for tuple containing color components. For most components without alpha it is 3 values
/// tuple. Alpha is always stored as the last component.
#[derive(Clone,Copy,Debug)]
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub struct Components<T> {
pub tuple : T
pub tuple: T,
}
/// Smart constructor.
#[allow(non_snake_case)]
pub fn Components<T>(tuple:T) -> Components<T> {
Components {tuple}
pub fn Components<T>(tuple: T) -> Components<T> {
Components { tuple }
}
impl<T> Components<T> {
/// Constructor.
pub fn new(tuple:T) -> Self {
Self {tuple}
pub fn new(tuple: T) -> Self {
Self { tuple }
}
}
@ -61,23 +61,29 @@ pub type ComponentsOf<T> = Components<ComponentsReprOf<T>>;
// === Generics ===
impl<T:KnownLast> KnownLast for Components<T> { type Last = Last<T>; }
impl<T:KnownInit> KnownInit for Components<T> { type Init = Components<Init<T>>; }
impl<T: KnownLast> KnownLast for Components<T> {
type Last = Last<T>;
}
impl<T: KnownInit> KnownInit for Components<T> {
type Init = Components<Init<T>>;
}
impl<T,X> PushBack<X> for Components<T>
where T:PushBack<X> {
impl<T, X> PushBack<X> for Components<T>
where T: PushBack<X>
{
type Output = Components<<T as PushBack<X>>::Output>;
fn push_back(self, t:X) -> Self::Output {
fn push_back(self, t: X) -> Self::Output {
Components(self.tuple.push_back(t))
}
}
impl<T> PopBack for Components<T>
where T:PopBack {
fn pop_back(self) -> (Self::Last,Self::Init) {
let (last,init) = self.tuple.pop_back();
where T: PopBack
{
fn pop_back(self) -> (Self::Last, Self::Init) {
let (last, init) = self.tuple.pop_back();
let init = Components(init);
(last,init)
(last, init)
}
}
@ -90,19 +96,19 @@ impl<T> PopBack for Components<T>
/// Allows mapping over `f32` components.
#[allow(missing_docs)]
pub trait ComponentMap {
fn map<F:Fn(f32)->f32>(&self, f:F) -> Self;
fn map<F: Fn(f32) -> f32>(&self, f: F) -> Self;
}
/// Trait for converting a type to its component representation.
pub trait ToComponents = Sized + HasComponentsRepr + Into<ComponentsOf<Self>>;
/// Trait for a component representation to the given type.
pub trait FromComponents = Sized + HasComponentsRepr where ComponentsOf<Self> : Into<Self>;
pub trait FromComponents = Sized + HasComponentsRepr where ComponentsOf<Self>: Into<Self>;
/// Trait allowing two way conversion of types and their component representations.
pub trait HasComponents : ToComponents + FromComponents {
pub trait HasComponents: ToComponents + FromComponents {
/// Convert components to the given type.
fn from_components(components:ComponentsOf<Self>) -> Self {
fn from_components(components: ComponentsOf<Self>) -> Self {
components.into()
}
@ -111,10 +117,10 @@ pub trait HasComponents : ToComponents + FromComponents {
self.into()
}
}
impl<T> HasComponents for T where T : ToComponents + FromComponents {}
impl<T> HasComponents for T where T: ToComponents + FromComponents {}
/// Convert components to the given type.
pub fn from_components<T:FromComponents>(components:ComponentsOf<T>) -> T {
pub fn from_components<T: FromComponents>(components: ComponentsOf<T>) -> T {
components.into()
}
@ -207,7 +213,7 @@ macro_rules! define_operators_for_component_refs {
)*}
}
define_operators_for_components! { Add::add, Sub::sub, Mul::mul, Div::div }
define_operators_for_components! { Add::add, Sub::sub, Mul::mul, Div::div }
define_operators_for_component_refs! { Add::add, Sub::sub, Mul::mul, Div::div }
@ -216,42 +222,48 @@ define_operators_for_component_refs! { Add::add, Sub::sub, Mul::mul, Div::div }
// === Vector Conversions ===
// ==========================
impl<T:Scalar> HasComponentsRepr for Vector2<T> { type ComponentsRepr = (T,T); }
impl<T:Scalar> HasComponentsRepr for Vector3<T> { type ComponentsRepr = (T,T,T); }
impl<T:Scalar> HasComponentsRepr for Vector4<T> { type ComponentsRepr = (T,T,T,T); }
impl<T: Scalar> HasComponentsRepr for Vector2<T> {
type ComponentsRepr = (T, T);
}
impl<T: Scalar> HasComponentsRepr for Vector3<T> {
type ComponentsRepr = (T, T, T);
}
impl<T: Scalar> HasComponentsRepr for Vector4<T> {
type ComponentsRepr = (T, T, T, T);
}
impl<T:Scalar> From<Vector2<T>> for ComponentsOf<Vector2<T>> {
fn from(t:Vector2<T>) -> Self {
Components((t.x.clone(),t.y.clone()))
impl<T: Scalar> From<Vector2<T>> for ComponentsOf<Vector2<T>> {
fn from(t: Vector2<T>) -> Self {
Components((t.x.clone(), t.y.clone()))
}
}
impl<T:Scalar> From<ComponentsOf<Vector2<T>>> for Vector2<T> {
fn from(value:ComponentsOf<Vector2<T>>) -> Self {
Vector2::new(value.0.clone(),value.1.clone())
impl<T: Scalar> From<ComponentsOf<Vector2<T>>> for Vector2<T> {
fn from(value: ComponentsOf<Vector2<T>>) -> Self {
Vector2::new(value.0.clone(), value.1.clone())
}
}
impl<T:Scalar> From<Vector3<T>> for ComponentsOf<Vector3<T>> {
fn from(t:Vector3<T>) -> Self {
Components((t.x.clone(),t.y.clone(),t.z.clone()))
impl<T: Scalar> From<Vector3<T>> for ComponentsOf<Vector3<T>> {
fn from(t: Vector3<T>) -> Self {
Components((t.x.clone(), t.y.clone(), t.z.clone()))
}
}
impl<T:Scalar> From<ComponentsOf<Vector3<T>>> for Vector3<T> {
fn from(value:ComponentsOf<Vector3<T>>) -> Self {
Vector3::new(value.0.clone(),value.1.clone(),value.2.clone())
impl<T: Scalar> From<ComponentsOf<Vector3<T>>> for Vector3<T> {
fn from(value: ComponentsOf<Vector3<T>>) -> Self {
Vector3::new(value.0.clone(), value.1.clone(), value.2.clone())
}
}
impl<T:Scalar> From<Vector4<T>> for ComponentsOf<Vector4<T>> {
fn from(t:Vector4<T>) -> Self {
Components((t.x.clone(),t.y.clone(),t.z.clone(),t.w.clone()))
impl<T: Scalar> From<Vector4<T>> for ComponentsOf<Vector4<T>> {
fn from(t: Vector4<T>) -> Self {
Components((t.x.clone(), t.y.clone(), t.z.clone(), t.w.clone()))
}
}
impl<T:Scalar> From<ComponentsOf<Vector4<T>>> for Vector4<T> {
fn from(value:ComponentsOf<Vector4<T>>) -> Self {
Vector4::new(value.0.clone(),value.1.clone(),value.2.clone(),value.3.clone())
impl<T: Scalar> From<ComponentsOf<Vector4<T>>> for Vector4<T> {
fn from(value: ComponentsOf<Vector4<T>>) -> Self {
Vector4::new(value.0.clone(), value.1.clone(), value.2.clone(), value.3.clone())
}
}

View File

@ -5,8 +5,8 @@ use crate::prelude::*;
use enso_generics::*;
use super::component::*;
use super::component::HasComponents;
use super::component::*;
use nalgebra::Vector3;
use nalgebra::Vector4;
@ -27,22 +27,22 @@ use nalgebra::Vector4;
/// just want it, for example to match the behavior of color mixing in web browsers, which is
/// broken for many years already:
/// https://stackoverflow.com/questions/60179850/webgl-2-0-canvas-blending-with-html-in-linear-color-space
#[derive(Clone,Copy,Default,PartialEq)]
#[derive(Clone, Copy, Default, PartialEq)]
pub struct Color<D> {
/// The underlying color representation. It is either `Alpha` or a color space instance.
pub data : D
pub data: D,
}
/// Smart constructor.
#[allow(non_snake_case)]
pub fn Color<D>(data:D) -> Color<D> {
Color {data}
pub fn Color<D>(data: D) -> Color<D> {
Color { data }
}
impl<D> Color<D> {
/// Return the color with an added alpha channel.
pub fn with_alpha(self, alpha:f32) -> Color<Alpha<D>> {
Color(Alpha{ alpha, opaque: self })
pub fn with_alpha(self, alpha: f32) -> Color<Alpha<D>> {
Color(Alpha { alpha, opaque: self })
}
}
@ -70,8 +70,12 @@ impl<D> DerefMut for Color<D> {
/// Type family for accessing color models.
#[allow(missing_docs)]
pub trait HasModel { type Model; }
impl<M> HasModel for Color<M> { type Model = M; }
pub trait HasModel {
type Model;
}
impl<M> HasModel for Color<M> {
type Model = M;
}
/// Accessor for `HasModel::Model`.
pub type Model<T> = <T as HasModel>::Model;
@ -79,99 +83,112 @@ pub type Model<T> = <T as HasModel>::Model;
// === Component Generics ===
impl<D:HasComponentsRepr> HasComponentsRepr for Color<D> {
impl<D: HasComponentsRepr> HasComponentsRepr for Color<D> {
type ComponentsRepr = ComponentsReprOf<D>;
}
impl<D:ComponentMap> ComponentMap for Color<D> {
fn map<F:Fn(f32)->f32>(&self, f:F) -> Self {
Self {data:self.data.map(f)}
impl<D: ComponentMap> ComponentMap for Color<D> {
fn map<F: Fn(f32) -> f32>(&self, f: F) -> Self {
Self { data: self.data.map(f) }
}
}
// === Conversions ===
impl<D1,D2> From<&Color<D1>> for Color<D2>
where Color<D1> : Clone + Into<Color<D2>> {
fn from(color:&Color<D1>) -> Self {
impl<D1, D2> From<&Color<D1>> for Color<D2>
where Color<D1>: Clone + Into<Color<D2>>
{
fn from(color: &Color<D1>) -> Self {
color.clone().into()
}
}
impl<C> From<Color<C>> for Color<Alpha<C>> {
fn from(color:Color<C>) -> Self {
fn from(color: Color<C>) -> Self {
let data = color.data.into();
Self {data}
Self { data }
}
}
impl<D> From<Color<D>> for ComponentsOf<Color<D>>
where D:HasComponents {
fn from(color:Color<D>) -> Self {
where D: HasComponents
{
fn from(color: Color<D>) -> Self {
color.data.into()
}
}
impl<D> From<ComponentsOf<D>> for Color<D>
where D:HasComponentsRepr, ComponentsOf<D>:Into<D> {
fn from(components:ComponentsOf<D>) -> Self {
Self {data:components.into()}
where
D: HasComponentsRepr,
ComponentsOf<D>: Into<D>,
{
fn from(components: ComponentsOf<D>) -> Self {
Self { data: components.into() }
}
}
impl<D> From<Color<D>> for Vector3<f32>
where Color<D> : HasComponents<ComponentsRepr=(f32,f32,f32)> {
fn from(value:Color<D>) -> Self {
where Color<D>: HasComponents<ComponentsRepr = (f32, f32, f32)>
{
fn from(value: Color<D>) -> Self {
Into::<Vector3<f32>>::into(value.into_components())
}
}
impl<D> From<Color<D>> for Vector4<f32>
where Color<D> : HasComponents<ComponentsRepr=(f32,f32,f32,f32)> {
fn from(value:Color<D>) -> Self {
where Color<D>: HasComponents<ComponentsRepr = (f32, f32, f32, f32)>
{
fn from(value: Color<D>) -> Self {
Into::<Vector4<f32>>::into(value.into_components())
}
}
impl<D> From<&Color<D>> for Vector3<f32>
where Color<D> : HasComponents<ComponentsRepr=(f32,f32,f32)> + Copy {
fn from(value:&Color<D>) -> Self {
where Color<D>: HasComponents<ComponentsRepr = (f32, f32, f32)> + Copy
{
fn from(value: &Color<D>) -> Self {
Into::<Vector3<f32>>::into(value.into_components())
}
}
impl<D> From<&Color<D>> for Vector4<f32>
where Color<D> : HasComponents<ComponentsRepr=(f32,f32,f32,f32)> + Copy {
fn from(value:&Color<D>) -> Self {
where Color<D>: HasComponents<ComponentsRepr = (f32, f32, f32, f32)> + Copy
{
fn from(value: &Color<D>) -> Self {
Into::<Vector4<f32>>::into(value.into_components())
}
}
impl<D> From<Vector3<f32>> for Color<D>
where D : HasComponents<ComponentsRepr=(f32,f32,f32)> {
fn from(t:Vector3<f32>) -> Self {
where D: HasComponents<ComponentsRepr = (f32, f32, f32)>
{
fn from(t: Vector3<f32>) -> Self {
Self::from(t.into_components())
}
}
impl<D> From<Vector4<f32>> for Color<D>
where D : HasComponents<ComponentsRepr=(f32,f32,f32,f32)> {
fn from(t:Vector4<f32>) -> Self {
where D: HasComponents<ComponentsRepr = (f32, f32, f32, f32)>
{
fn from(t: Vector4<f32>) -> Self {
Self::from(t.into_components())
}
}
impl<D> From<&Vector3<f32>> for Color<D>
where D : Copy + HasComponents<ComponentsRepr=(f32,f32,f32)> {
fn from(t:&Vector3<f32>) -> Self {
where D: Copy + HasComponents<ComponentsRepr = (f32, f32, f32)>
{
fn from(t: &Vector3<f32>) -> Self {
Self::from((*t).into_components())
}
}
impl<D> From<&Vector4<f32>> for Color<D>
where D : Copy + HasComponents<ComponentsRepr=(f32,f32,f32,f32)> {
fn from(t:&Vector4<f32>) -> Self {
where D: Copy + HasComponents<ComponentsRepr = (f32, f32, f32, f32)>
{
fn from(t: &Vector4<f32>) -> Self {
Self::from((*t).into_components())
}
}
@ -228,7 +245,7 @@ macro_rules! define_color_operators {
)*}
}
define_color_operators!{ Add::add, Sub::sub, Mul::mul, Div::div }
define_color_operators! { Add::add, Sub::sub, Mul::mul, Div::div }
@ -237,44 +254,53 @@ define_color_operators!{ Add::add, Sub::sub, Mul::mul, Div::div }
// =============
/// An alpha component wrapper for colors. 0.0 is fully transparent and 1.0 is fully opaque.
#[derive(Clone,Copy,PartialEq)]
#[derive(Clone, Copy, PartialEq)]
#[allow(missing_docs)]
#[allow(missing_debug_implementations)]
pub struct Alpha<C> {
pub alpha : f32,
pub opaque : Color<C>,
pub alpha: f32,
pub opaque: Color<C>,
}
// === Component Generics ===
impl<C> HasComponentsRepr for Alpha<C>
where C:HasComponentsRepr, ComponentsReprOf<C>:PushBack<f32> {
where
C: HasComponentsRepr,
ComponentsReprOf<C>: PushBack<f32>,
{
type ComponentsRepr = <ComponentsReprOf<C> as PushBack<f32>>::Output;
}
impl<C> From<Alpha<C>> for ComponentsOf<Alpha<C>>
where C:HasComponents, ComponentsReprOf<C>:PushBack<f32> {
fn from(t:Alpha<C>) -> Self {
where
C: HasComponents,
ComponentsReprOf<C>: PushBack<f32>,
{
fn from(t: Alpha<C>) -> Self {
t.opaque.data.into().push_back(t.alpha)
}
}
impl<C> From<ComponentsOf<Alpha<C>>> for Alpha<C>
where C:HasComponents, ComponentsReprOf<C>:PushBack<f32>,
<ComponentsReprOf<C> as PushBack<f32>>::Output : PopBack<Last=f32,Init=ComponentsReprOf<C>> {
fn from(components:ComponentsOf<Self>) -> Self {
let (alpha,init) = components.pop_back();
let opaque = from_components(init);
Self {alpha,opaque}
where
C: HasComponents,
ComponentsReprOf<C>: PushBack<f32>,
<ComponentsReprOf<C> as PushBack<f32>>::Output: PopBack<Last = f32, Init = ComponentsReprOf<C>>,
{
fn from(components: ComponentsOf<Self>) -> Self {
let (alpha, init) = components.pop_back();
let opaque = from_components(init);
Self { alpha, opaque }
}
}
impl<C:ComponentMap> ComponentMap for Alpha<C> {
fn map<F:Fn(f32)->f32>(&self, f:F) -> Self {
let alpha = f(self.alpha);
impl<C: ComponentMap> ComponentMap for Alpha<C> {
fn map<F: Fn(f32) -> f32>(&self, f: F) -> Self {
let alpha = f(self.alpha);
let opaque = self.opaque.map(f);
Self {alpha,opaque}
Self { alpha, opaque }
}
}
@ -286,30 +312,30 @@ impl<C> Deref for Alpha<C> {
}
impl<C> From<C> for Alpha<C> {
fn from(data:C) -> Self {
let alpha = 1.0;
let opaque = Color {data};
Self {alpha,opaque}
fn from(data: C) -> Self {
let alpha = 1.0;
let opaque = Color { data };
Self { alpha, opaque }
}
}
impl<C:Default> Default for Alpha<C> {
impl<C: Default> Default for Alpha<C> {
fn default() -> Self {
let alpha = 1.0;
let alpha = 1.0;
let opaque = default();
Self {alpha,opaque}
Self { alpha, opaque }
}
}
impl<C> Alpha<C> {
/// Return the color with a multiplied alpha channel.
pub fn multiply_alpha(self, alpha:f32) -> Color<Self> {
pub fn multiply_alpha(self, alpha: f32) -> Color<Self> {
let alpha = self.alpha * alpha;
Color(Alpha{alpha, opaque: self.opaque })
Color(Alpha { alpha, opaque: self.opaque })
}
/// Modify the color's alpha channel.
pub fn mod_alpha<F:FnOnce(&mut f32)>(&mut self, f:F) {
pub fn mod_alpha<F: FnOnce(&mut f32)>(&mut self, f: F) {
f(&mut self.alpha)
}
}

View File

@ -2,8 +2,8 @@
use crate::prelude::*;
use crate::system::gpu::shader::glsl::Glsl;
use crate::system::gpu::shader::glsl::traits::*;
use crate::system::gpu::shader::glsl::Glsl;
@ -13,18 +13,18 @@ use crate::system::gpu::shader::glsl::traits::*;
/// Control point of the gradient. It defines a color at a specific gradient offset. The offset
/// of `0` means the beginning of the gradient. The offset of `1` means its end.
#[derive(Clone,Debug,Default)]
#[derive(Clone, Debug, Default)]
pub struct ControlPoint<Color> {
/// Offset of the control point in [0..1] range.
pub offset : f32,
pub offset: f32,
/// Color of this control point.
pub color : Color
pub color: Color,
}
impl<Color> ControlPoint<Color> {
/// Constructor.
pub fn new(offset:f32, color:Color) -> Self {
Self {offset,color}
pub fn new(offset: f32, color: Color) -> Self {
Self { offset, color }
}
}
@ -36,10 +36,10 @@ impl<Color> ControlPoint<Color> {
/// A range of position-dependent colors encoded as control points. Control points do not contain
/// any information about incoming or outgoing slope, so the interpolation between them is linear.
#[derive(Clone,Debug,Derivative)]
#[derivative(Default(bound=""))]
#[derive(Clone, Debug, Derivative)]
#[derivative(Default(bound = ""))]
pub struct Linear<Color> {
control_points : Vec<ControlPoint<Color>>,
control_points: Vec<ControlPoint<Color>>,
}
impl<Color> Linear<Color> {
@ -49,14 +49,14 @@ impl<Color> Linear<Color> {
}
/// Constructor.
pub fn new(start:impl Into<Color>, end:impl Into<Color>) -> Self {
pub fn new(start: impl Into<Color>, end: impl Into<Color>) -> Self {
let this = Self::empty();
this.add(0.0,start).add(1.0,end)
this.add(0.0, start).add(1.0, end)
}
/// Add a new control point. The offset needs to be in range [0..1].
pub fn add(mut self, offset:f32, color:impl Into<Color>) -> Self {
self.control_points.push(ControlPoint::new(offset,color.into()));
pub fn add(mut self, offset: f32, color: impl Into<Color>) -> Self {
self.control_points.push(ControlPoint::new(offset, color.into()));
self
}
@ -85,50 +85,50 @@ where [Color:RefInto<Glsl>] {
// ==================
/// Default start distance of the distance gradient.
pub const DEFAULT_DISTANCE_GRADIENT_SPREAD : f32 = 0.0;
pub const DEFAULT_DISTANCE_GRADIENT_SPREAD: f32 = 0.0;
/// Default end distance of the distance gradient.
pub const DEFAULT_DISTANCE_GRADIENT_SIZE : f32 = 10.0;
pub const DEFAULT_DISTANCE_GRADIENT_SIZE: f32 = 10.0;
/// A gradient which transforms a linear gradient to a gradient along the signed distance field.
/// The slope parameter modifies how fast the gradient values are changed, allowing for nice,
/// smooth transitions.
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
pub struct SdfSampler<Gradient> {
/// The distance from the shape border at which the gradient should start.
pub spread : f32,
pub spread: f32,
/// The size of the gradient in the SDF space.
pub size : f32,
pub size: f32,
/// The gradient slope modifier. Defines how fast the gradient values change.
pub slope : Slope,
pub slope: Slope,
/// The underlying gradient.
pub gradient : Gradient
pub gradient: Gradient,
}
impl<Gradient> SdfSampler<Gradient> {
/// Constructs a new gradient with `spread` and `size` set to
/// `DEFAULT_DISTANCE_GRADIENT_SPREAD` and `DEFAULT_DISTANCE_GRADIENT_SIZE` respectively.
pub fn new(gradient:Gradient) -> Self {
pub fn new(gradient: Gradient) -> Self {
let spread = DEFAULT_DISTANCE_GRADIENT_SPREAD;
let size = DEFAULT_DISTANCE_GRADIENT_SIZE;
let slope = Slope::Smooth;
Self {spread,size,slope,gradient}
let size = DEFAULT_DISTANCE_GRADIENT_SIZE;
let slope = Slope::Smooth;
Self { spread, size, slope, gradient }
}
/// Constructor setter for the `spread` field.
pub fn spread(mut self, t:f32) -> Self {
pub fn spread(mut self, t: f32) -> Self {
self.spread = t;
self
}
/// Constructor setter for the `size` field.
pub fn size(mut self, t:f32) -> Self {
pub fn size(mut self, t: f32) -> Self {
self.size = t;
self
}
/// Constructor setter for the `slope` field.
pub fn slope(mut self, t:Slope) -> Self {
pub fn slope(mut self, t: Slope) -> Self {
self.slope = t;
self
}

View File

@ -1,7 +1,7 @@
//! Color mixing utilities.
use crate::prelude::*;
use super::*;
use crate::prelude::*;
use crate::data::mix;
use crate::data::mix::Mixable;
@ -33,20 +33,22 @@ macro_rules! define_mix_impls {
macro_rules! define_mix_impl_repr {
($tp:ty => $via_tp:ty [$repr:ident]) => {
impl Mixable for $tp { type Repr = $repr; }
impl Mixable for $tp {
type Repr = $repr;
}
impl From<$tp> for mix::Space<$tp> {
fn from(value:$tp) -> mix::Space<$tp> {
fn from(value: $tp) -> mix::Space<$tp> {
mix::Space::new(<$via_tp>::from(value).into())
}
}
impl From<mix::Space<$tp>> for $tp {
fn from(t:mix::Space<$tp>) -> Self {
fn from(t: mix::Space<$tp>) -> Self {
<$via_tp>::from(t.value).into()
}
}
}
};
}

View File

@ -14,11 +14,11 @@
#![allow(clippy::unreadable_literal)]
#![allow(clippy::excessive_precision)]
use super::def::*;
use super::super::component::*;
use super::super::data::*;
use super::white_point::traits::*;
use super::def::*;
use super::white_point;
use super::white_point::traits::*;
@ -67,41 +67,41 @@ macro_rules! color_convert_via {
($src:ident -> $via:ident -> $tgt:ident) => {
impl From<$src> for $tgt {
fn from(src:$src) -> Self {
fn from(src: $src) -> Self {
$via::from(src).into()
}
}
impl From<Color<$src>> for Color<$tgt> {
fn from(src:Color<$src>) -> Self {
fn from(src: Color<$src>) -> Self {
<Color<$via>>::from(src).into()
}
}
impl From<Alpha<$src>> for Alpha<$tgt> {
fn from(src:Alpha<$src>) -> Self {
fn from(src: Alpha<$src>) -> Self {
<Alpha<$via>>::from(src).into()
}
}
impl From<Color<Alpha<$src>>> for Color<Alpha<$tgt>> {
fn from(src:Color<Alpha<$src>>) -> Self {
fn from(src: Color<Alpha<$src>>) -> Self {
<Color<Alpha<$via>>>::from(src).into()
}
}
impl From<Color<Alpha<$src>>> for Color<$tgt> {
fn from(src:Color<Alpha<$src>>) -> Self {
fn from(src: Color<Alpha<$src>>) -> Self {
<Color<$via>>::from(src.opaque).into()
}
}
impl From<Color<$src>> for Color<Alpha<$tgt>> {
fn from(src:Color<$src>) -> Self {
fn from(src: Color<$src>) -> Self {
<Color<Alpha<$src>>>::from(src).into()
}
}
}
};
}
@ -112,14 +112,20 @@ macro_rules! color_convert_via {
/// More info: http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
fn into_linear(x:f32) -> f32 {
if x <= 0.04045 { x / 12.92 }
else { ((x + 0.055) / 1.055).powf(2.4) }
fn into_linear(x: f32) -> f32 {
if x <= 0.04045 {
x / 12.92
} else {
((x + 0.055) / 1.055).powf(2.4)
}
}
fn from_linear(x:f32) -> f32 {
if x <= 0.0031308 { x * 12.92 }
else { x.powf(1.0/2.4) * 1.055 - 0.055 }
fn from_linear(x: f32) -> f32 {
if x <= 0.0031308 {
x * 12.92
} else {
x.powf(1.0 / 2.4) * 1.055 - 0.055
}
}
color_conversion! {
@ -216,12 +222,12 @@ impl From<XyzData> for LinearRgbData {
impl LabData {
/// Normalize the a* or b* value from range [-128 .. 127] to [-1 .. 1].
fn normalize_a_b(t:f32) -> f32 {
fn normalize_a_b(t: f32) -> f32 {
(2.0 * (t + 128.0) / 255.0) - 1.0
}
/// Denormalize the a* or b* value from range [-1 .. 1] to [-128 .. 127].
fn denormalize_a_b(t:f32) -> f32 {
fn denormalize_a_b(t: f32) -> f32 {
(255.0 * (t + 1.0) / 2.0) - 128.0
}
}
@ -279,13 +285,13 @@ impl From<LabData> for XyzData {
impl LchData {
/// Normalize the a* or b* value from range [0 .. `LCH_MAX_CHROMA_IN_SRGB_IN_STD_EQUATIONS`]
/// to [0 .. 1].
fn normalize_chroma(t:f32) -> f32 {
fn normalize_chroma(t: f32) -> f32 {
t / LCH_MAX_CHROMA_IN_SRGB_IN_STD_EQUATIONS as f32
}
/// Denormalize the a* or b* value from range [0 .. 1] to
/// [0 .. `LCH_MAX_CHROMA_IN_SRGB_IN_STD_EQUATIONS`].
fn denormalize_chroma(t:f32) -> f32 {
fn denormalize_chroma(t: f32) -> f32 {
t * LCH_MAX_CHROMA_IN_SRGB_IN_STD_EQUATIONS as f32
}
}
@ -345,18 +351,18 @@ mod tests {
#[test]
#[allow(unused_variables)]
fn test_rgb_to_and_from_lch() {
for r in 0 .. 10 {
for g in 0 .. 10 {
for b in 0 .. 10 {
let nr = (r as f32) / 255.0;
let ng = (g as f32) / 255.0;
let nb = (b as f32) / 255.0;
let rgb = Rgb::new(nr,ng,nb);
let lch = Lch::from(rgb);
for r in 0..10 {
for g in 0..10 {
for b in 0..10 {
let nr = (r as f32) / 255.0;
let ng = (g as f32) / 255.0;
let nb = (b as f32) / 255.0;
let rgb = Rgb::new(nr, ng, nb);
let lch = Lch::from(rgb);
let rgb2 = Rgb::from(lch);
let _r2 = (rgb2.red * 255.0) as i32;
let _g2 = (rgb2.green * 255.0) as i32;
let _b2 = (rgb2.blue * 255.0) as i32;
let _r2 = (rgb2.red * 255.0) as i32;
let _g2 = (rgb2.green * 255.0) as i32;
let _b2 = (rgb2.blue * 255.0) as i32;
// assert_eq!((r,g,b),(r2,g2,b2));
}
}

View File

@ -1,8 +1,8 @@
//! This module contains definitions of various color spaces, including `Rgb`, `Hsl`, `Lch`, etc.
use crate::prelude::*;
use super::super::data::*;
use super::super::component::*;
use super::super::data::*;
use crate::prelude::*;
@ -14,15 +14,18 @@ macro_rules! define_color_parsing {
($name:ident) => {
impl std::str::FromStr for $name {
type Err = ParseError;
fn from_str(s:&str) -> Result<Self, Self::Err> {
let (head,args) = generic_parse(s)?;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (head, args) = generic_parse(s)?;
if &head != stringify!($name) {
return Err(ParseError::new(format!("No '{}' header found.",stringify!($name))))
return Err(ParseError::new(format!(
"No '{}' header found.",
stringify!($name)
)));
}
Ok($name::from_slice(&args))
}
}
}
};
}
macro_rules! define_color_spaces {
@ -73,19 +76,23 @@ macro_rules! define_color_spaces {
}
impl<C> From<AnyFormat> for Color<C>
where Rgb: Into<Color<C>>, Rgba: Into<Color<C>>,
Lch: Into<Color<C>>, Lcha: Into<Color<C>> {
where
Rgb: Into<Color<C>>,
Rgba: Into<Color<C>>,
Lch: Into<Color<C>>,
Lcha: Into<Color<C>>,
{
fn from(c: AnyFormat) -> Self {
match c {
AnyFormat::Rgb(t) => t.into(),
AnyFormat::Rgb(t) => t.into(),
AnyFormat::Rgba(t) => t.into(),
AnyFormat::Lch(t) => t.into(),
AnyFormat::Lch(t) => t.into(),
AnyFormat::Lcha(t) => t.into(),
// TODO[WD]: This should be implemented by the commented out macro above, however,
// it requires a lot more conversions than we support currently. To be implemented
// one day.
// https://github.com/enso-org/ide/issues/1404
_ => panic!("Not implemented.")
_ => panic!("Not implemented."),
}
}
}
@ -378,7 +385,7 @@ define_color_spaces! {
impl Rgb {
/// Construct RGB color by mapping [0 255] value range into [0.0 1.0].
pub fn from_base_255(r:impl Into<f32>, g:impl Into<f32>, b:impl Into<f32>) -> Self {
pub fn from_base_255(r: impl Into<f32>, g: impl Into<f32>, b: impl Into<f32>) -> Self {
Self::new(r.into() / 255.0, g.into() / 255.0, b.into() / 255.0)
}
@ -389,42 +396,42 @@ impl Rgb {
/// Convert the color to JavaScript representation.
pub fn to_javascript_string(self) -> String {
let red = (self.red*255.0).round() as i32;
let green = (self.green*255.0).round() as i32;
let blue = (self.blue*255.0).round() as i32;
format!("rgb({},{},{})",red,green,blue)
let red = (self.red * 255.0).round() as i32;
let green = (self.green * 255.0).round() as i32;
let blue = (self.blue * 255.0).round() as i32;
format!("rgb({},{},{})", red, green, blue)
}
}
impl Rgba {
/// Constructor.
pub fn black() -> Self {
Self::new(0.0,0.0,0.0,1.0)
Self::new(0.0, 0.0, 0.0, 1.0)
}
/// Constructor.
pub fn white() -> Self {
Self::new(1.0,1.0,1.0,1.0)
Self::new(1.0, 1.0, 1.0, 1.0)
}
/// Constructor.
pub fn red() -> Self {
Self::new(1.0,0.0,0.0,1.0)
Self::new(1.0, 0.0, 0.0, 1.0)
}
/// Constructor.
pub fn green() -> Self {
Self::new(0.0,1.0,0.0,1.0)
Self::new(0.0, 1.0, 0.0, 1.0)
}
/// Constructor.
pub fn blue() -> Self {
Self::new(0.0,0.0,1.0,1.0)
Self::new(0.0, 0.0, 1.0, 1.0)
}
/// Fully transparent color constructor.
pub fn transparent() -> Self {
Self::new(0.0,0.0,0.0,0.0)
Self::new(0.0, 0.0, 0.0, 0.0)
}
/// Convert the color to `LinearRgba` representation.
@ -434,10 +441,10 @@ impl Rgba {
/// Convert the color to JavaScript representation.
pub fn to_javascript_string(self) -> String {
let red = (self.red*255.0).round() as i32;
let green = (self.green*255.0).round() as i32;
let blue = (self.blue*255.0).round() as i32;
format!("rgba({},{},{},{})",red,green,blue,self.alpha)
let red = (self.red * 255.0).round() as i32;
let green = (self.green * 255.0).round() as i32;
let blue = (self.blue * 255.0).round() as i32;
format!("rgba({},{},{},{})", red, green, blue, self.alpha)
}
}
@ -465,7 +472,9 @@ impl LabData {
None
} else {
let mut hue = self.b.atan2(self.a) * 180.0 / std::f32::consts::PI;
if hue < 0.0 { hue += 360.0 }
if hue < 0.0 {
hue += 360.0
}
Some(hue)
}
}
@ -481,50 +490,114 @@ impl LabData {
/// uses scale used by popular color-conversion math equations, such as https://css.land/lch, or
/// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html. Used internally for color
/// conversions.
pub(crate) const LCH_MAX_CHROMA_IN_SRGB_IN_STD_EQUATIONS : usize = 120;
pub(crate) const LCH_MAX_CHROMA_IN_SRGB_IN_STD_EQUATIONS: usize = 120;
#[allow(missing_docs)]
impl Lch {
pub fn pink_hue () -> f32 { 0.0 } // approx. 0.0 degrees
pub fn red_hue () -> f32 { 0.111 } // approx. 40.0 degrees
pub fn orange_hue () -> f32 { 0.18 } // approx. 65.0 degrees
pub fn yellow_hue () -> f32 { 0.236 } // approx. 85.0 degrees
pub fn olive_hue () -> f32 { 0.291 } // approx. 105.0 degrees
pub fn green_hue () -> f32 { 0.378 } // approx. 136.0 degrees
pub fn blue_green_hue () -> f32 { 0.6 } // approx. 216.0 degrees
pub fn blue_hue () -> f32 { 0.672 } // approx. 242.0 degrees
pub fn violet_hue () -> f32 { 0.847 } // approx. 305.0 degrees
pub fn pink_hue() -> f32 {
0.0
} // approx. 0.0 degrees
pub fn red_hue() -> f32 {
0.111
} // approx. 40.0 degrees
pub fn orange_hue() -> f32 {
0.18
} // approx. 65.0 degrees
pub fn yellow_hue() -> f32 {
0.236
} // approx. 85.0 degrees
pub fn olive_hue() -> f32 {
0.291
} // approx. 105.0 degrees
pub fn green_hue() -> f32 {
0.378
} // approx. 136.0 degrees
pub fn blue_green_hue() -> f32 {
0.6
} // approx. 216.0 degrees
pub fn blue_hue() -> f32 {
0.672
} // approx. 242.0 degrees
pub fn violet_hue() -> f32 {
0.847
} // approx. 305.0 degrees
}
#[allow(missing_docs)]
impl Lch {
pub fn white () -> Lch { Lch::new(1.0,0.0,0.0) }
pub fn black () -> Lch { Lch::new(0.0,0.0,0.0) }
pub fn pink (l:f32, c:f32) -> Lch { Lch::new(l,c,Lch::pink_hue()) }
pub fn red (l:f32, c:f32) -> Lch { Lch::new(l,c,Lch::red_hue()) }
pub fn orange (l:f32, c:f32) -> Lch { Lch::new(l,c,Lch::orange_hue()) }
pub fn yellow (l:f32, c:f32) -> Lch { Lch::new(l,c,Lch::yellow_hue()) }
pub fn olive (l:f32, c:f32) -> Lch { Lch::new(l,c,Lch::olive_hue()) }
pub fn green (l:f32, c:f32) -> Lch { Lch::new(l,c,Lch::green_hue()) }
pub fn blue_green (l:f32, c:f32) -> Lch { Lch::new(l,c,Lch::blue_green_hue()) }
pub fn blue (l:f32, c:f32) -> Lch { Lch::new(l,c,Lch::blue_hue()) }
pub fn violet (l:f32, c:f32) -> Lch { Lch::new(l,c,Lch::violet_hue()) }
pub fn white() -> Lch {
Lch::new(1.0, 0.0, 0.0)
}
pub fn black() -> Lch {
Lch::new(0.0, 0.0, 0.0)
}
pub fn pink(l: f32, c: f32) -> Lch {
Lch::new(l, c, Lch::pink_hue())
}
pub fn red(l: f32, c: f32) -> Lch {
Lch::new(l, c, Lch::red_hue())
}
pub fn orange(l: f32, c: f32) -> Lch {
Lch::new(l, c, Lch::orange_hue())
}
pub fn yellow(l: f32, c: f32) -> Lch {
Lch::new(l, c, Lch::yellow_hue())
}
pub fn olive(l: f32, c: f32) -> Lch {
Lch::new(l, c, Lch::olive_hue())
}
pub fn green(l: f32, c: f32) -> Lch {
Lch::new(l, c, Lch::green_hue())
}
pub fn blue_green(l: f32, c: f32) -> Lch {
Lch::new(l, c, Lch::blue_green_hue())
}
pub fn blue(l: f32, c: f32) -> Lch {
Lch::new(l, c, Lch::blue_hue())
}
pub fn violet(l: f32, c: f32) -> Lch {
Lch::new(l, c, Lch::violet_hue())
}
}
#[allow(missing_docs)]
impl Lcha {
pub fn transparent () -> Lcha { Lcha::new(0.0,0.0,0.0,0.0) }
pub fn white () -> Lcha { Lch::white () . into() }
pub fn black () -> Lcha { Lch::black () . into() }
pub fn pink (l:f32, c:f32) -> Lcha { Lch::pink (l,c) . into() }
pub fn red (l:f32, c:f32) -> Lcha { Lch::red (l,c) . into() }
pub fn orange (l:f32, c:f32) -> Lcha { Lch::orange (l,c) . into() }
pub fn yellow (l:f32, c:f32) -> Lcha { Lch::yellow (l,c) . into() }
pub fn olive (l:f32, c:f32) -> Lcha { Lch::olive (l,c) . into() }
pub fn green (l:f32, c:f32) -> Lcha { Lch::green (l,c) . into() }
pub fn blue_green (l:f32, c:f32) -> Lcha { Lch::blue_green (l,c) . into() }
pub fn blue (l:f32, c:f32) -> Lcha { Lch::blue (l,c) . into() }
pub fn violet (l:f32, c:f32) -> Lcha { Lch::violet (l,c) . into() }
pub fn transparent() -> Lcha {
Lcha::new(0.0, 0.0, 0.0, 0.0)
}
pub fn white() -> Lcha {
Lch::white().into()
}
pub fn black() -> Lcha {
Lch::black().into()
}
pub fn pink(l: f32, c: f32) -> Lcha {
Lch::pink(l, c).into()
}
pub fn red(l: f32, c: f32) -> Lcha {
Lch::red(l, c).into()
}
pub fn orange(l: f32, c: f32) -> Lcha {
Lch::orange(l, c).into()
}
pub fn yellow(l: f32, c: f32) -> Lcha {
Lch::yellow(l, c).into()
}
pub fn olive(l: f32, c: f32) -> Lcha {
Lch::olive(l, c).into()
}
pub fn green(l: f32, c: f32) -> Lcha {
Lch::green(l, c).into()
}
pub fn blue_green(l: f32, c: f32) -> Lcha {
Lch::blue_green(l, c).into()
}
pub fn blue(l: f32, c: f32) -> Lcha {
Lch::blue(l, c).into()
}
pub fn violet(l: f32, c: f32) -> Lcha {
Lch::violet(l, c).into()
}
/// Convert the color to JavaScript representation.
pub fn to_javascript_string(self) -> String {
@ -576,16 +649,109 @@ impl Lcha {
/// 0 12.5 25.0 37.5 50.0 62.5 75.0 75.5 100.0
/// LIGHTNESS
/// ```
pub const LCH_MAX_LIGHTNESS_CHROMA_IN_SRGB_CORRELATION : &[(usize,usize)] =
&[(0,0),(1,1),(2,2),(3,5),(4,5),(5,6),(6,8),(7,9),(8,10),(9,11),(10,12),(11,13),(12,13),(13,14)
,(14,14),(15,15),(16,15),(17,16),(18,16),(19,17),(20,17),(21,18),(22,18),(23,18),(24,19)
,(25,19),(26,20),(27,20),(28,21),(29,21),(30,22),(31,22),(32,23),(33,23),(34,24),(35,24)
,(36,25),(37,25),(38,26),(39,26),(40,27),(41,27),(42,28),(43,28),(44,29),(45,29),(46,30)
,(47,30),(48,31),(49,31),(50,32),(51,32),(52,33),(53,33),(54,34),(55,34),(56,35),(57,35)
,(58,36),(59,36),(60,36),(61,37),(62,37),(63,38),(64,38),(65,39),(66,39),(67,40),(68,40)
,(69,41),(70,41),(71,42),(72,42),(73,41),(74,39),(75,38),(76,36),(77,35),(78,33),(79,31)
,(80,30),(81,28),(82,27),(83,25),(84,24),(85,22),(86,20),(87,19),(88,17),(89,16),(90,14)
,(91,12),(92,11),(93,9),(94,8),(95,6),(96,5),(97,4),(98,2),(99,1),(100,0)];
pub const LCH_MAX_LIGHTNESS_CHROMA_IN_SRGB_CORRELATION: &[(usize, usize)] = &[
(0, 0),
(1, 1),
(2, 2),
(3, 5),
(4, 5),
(5, 6),
(6, 8),
(7, 9),
(8, 10),
(9, 11),
(10, 12),
(11, 13),
(12, 13),
(13, 14),
(14, 14),
(15, 15),
(16, 15),
(17, 16),
(18, 16),
(19, 17),
(20, 17),
(21, 18),
(22, 18),
(23, 18),
(24, 19),
(25, 19),
(26, 20),
(27, 20),
(28, 21),
(29, 21),
(30, 22),
(31, 22),
(32, 23),
(33, 23),
(34, 24),
(35, 24),
(36, 25),
(37, 25),
(38, 26),
(39, 26),
(40, 27),
(41, 27),
(42, 28),
(43, 28),
(44, 29),
(45, 29),
(46, 30),
(47, 30),
(48, 31),
(49, 31),
(50, 32),
(51, 32),
(52, 33),
(53, 33),
(54, 34),
(55, 34),
(56, 35),
(57, 35),
(58, 36),
(59, 36),
(60, 36),
(61, 37),
(62, 37),
(63, 38),
(64, 38),
(65, 39),
(66, 39),
(67, 40),
(68, 40),
(69, 41),
(70, 41),
(71, 42),
(72, 42),
(73, 41),
(74, 39),
(75, 38),
(76, 36),
(77, 35),
(78, 33),
(79, 31),
(80, 30),
(81, 28),
(82, 27),
(83, 25),
(84, 24),
(85, 22),
(86, 20),
(87, 19),
(88, 17),
(89, 16),
(90, 14),
(91, 12),
(92, 11),
(93, 9),
(94, 8),
(95, 6),
(96, 5),
(97, 4),
(98, 2),
(99, 1),
(100, 0),
];
lazy_static! {
/// Map from LCH lightness to max chroma, so every hue value will be included in the sRGB color
@ -620,19 +786,19 @@ lazy_static! {
/// For a given LCH lightness, compute the max chroma value, so every hue value will be included in
/// the sRGB color space. Please read the docs of `LCH_MAX_LIGHTNESS_CHROMA_IN_SRGB_CORRELATION` to
/// learn more.
fn lch_lightness_to_max_chroma_in_srgb(l:f32) -> f32 {
let l = l.max(0.0).min(100.0);
let l_scaled = l * 100.0;
let l_scaled_floor = l_scaled.floor();
let l_scaled_ceil = l_scaled.ceil();
let coeff = (l_scaled - l_scaled_floor) / (l_scaled_ceil - l_scaled_floor);
fn lch_lightness_to_max_chroma_in_srgb(l: f32) -> f32 {
let l = l.max(0.0).min(100.0);
let l_scaled = l * 100.0;
let l_scaled_floor = l_scaled.floor();
let l_scaled_ceil = l_scaled.ceil();
let coeff = (l_scaled - l_scaled_floor) / (l_scaled_ceil - l_scaled_floor);
let l_scaled_floor_u = l_scaled_floor as usize;
let l_scaled_ceil_u = l_scaled_ceil as usize;
let l_scaled_ceil_u = l_scaled_ceil as usize;
let c_scaled_floor_u = LCH_LIGHTNESS_TO_MAX_CHROMA_IN_SRGB.get(&l_scaled_floor_u);
let c_scaled_ceil_u = LCH_LIGHTNESS_TO_MAX_CHROMA_IN_SRGB.get(&l_scaled_ceil_u);
let c_scaled_floor = c_scaled_floor_u.copied().unwrap_or(0) as f32;
let c_scaled_ceil = c_scaled_ceil_u.copied().unwrap_or(0) as f32;
let c_scaled = c_scaled_floor + (c_scaled_ceil - c_scaled_floor) * coeff;
let c_scaled_ceil_u = LCH_LIGHTNESS_TO_MAX_CHROMA_IN_SRGB.get(&l_scaled_ceil_u);
let c_scaled_floor = c_scaled_floor_u.copied().unwrap_or(0) as f32;
let c_scaled_ceil = c_scaled_ceil_u.copied().unwrap_or(0) as f32;
let c_scaled = c_scaled_floor + (c_scaled_ceil - c_scaled_floor) * coeff;
c_scaled / LCH_MAX_CHROMA_IN_SRGB_IN_STD_EQUATIONS as f32
}
@ -643,56 +809,54 @@ fn lch_lightness_to_max_chroma_in_srgb(l:f32) -> f32 {
// ===============
/// String to color parse error.
#[derive(Debug,Clone)]
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub struct ParseError {
pub reason:String
pub reason: String,
}
impl ParseError {
/// Constructor.
pub fn new(reason:impl Into<String>) -> Self {
pub fn new(reason: impl Into<String>) -> Self {
let reason = reason.into();
Self {reason}
Self { reason }
}
}
impl From<std::num::ParseFloatError> for ParseError {
fn from(_:std::num::ParseFloatError) -> Self {
fn from(_: std::num::ParseFloatError) -> Self {
ParseError::new("Improper numeric argument.")
}
}
fn uppercase_first_letter(s:&str) -> String {
fn uppercase_first_letter(s: &str) -> String {
let mut c = s.chars();
match c.next() {
None => String::new(),
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
}
}
/// Consume the input string and return the header and list of args. For example, for the input
/// `rgba(1.0,0.0,0.0,0.5)`, the header will be `"rgba"`, and the nubers will be arguments.
fn generic_parse(s:&str) -> Result<(String,Vec<f32>),ParseError> {
let mut splitter = s.splitn(2,'(');
fn generic_parse(s: &str) -> Result<(String, Vec<f32>), ParseError> {
let mut splitter = s.splitn(2, '(');
match splitter.next() {
None => Err(ParseError::new("Empty input.")),
Some(head) => {
match splitter.next() {
None => Err(ParseError::new("No arguments provided.")),
Some(rest) => {
let head = uppercase_first_letter(&head.to_lowercase());
if !rest.ends_with(')') {
Err(ParseError::new("Expression does not end with ')'."))
} else {
let rest = &rest[..rest.len()-1];
let args : Result<Vec<f32>,std::num::ParseFloatError> =
rest.split(',').map(|t|t.parse::<f32>()).collect();
let args = args?;
Ok((head,args))
}
None => Err(ParseError::new("Empty input.")),
Some(head) => match splitter.next() {
None => Err(ParseError::new("No arguments provided.")),
Some(rest) => {
let head = uppercase_first_letter(&head.to_lowercase());
if !rest.ends_with(')') {
Err(ParseError::new("Expression does not end with ')'."))
} else {
let rest = &rest[..rest.len() - 1];
let args: Result<Vec<f32>, std::num::ParseFloatError> =
rest.split(',').map(|t| t.parse::<f32>()).collect();
let args = args?;
Ok((head, args))
}
}
}
},
}
}

View File

@ -9,8 +9,8 @@
//! Note that this module implements currently only `D65` white point. In case other will be needed
//! they can be easily ported from the Rust `Palette` library implementation.
use super::def::*;
use super::super::component::*;
use super::def::*;
/// Common traits.
@ -50,6 +50,6 @@ pub trait WhitePoint {
pub struct D65;
impl WhitePoint for D65 {
fn get_xyz() -> Xyz {
from_components(Components((0.95047,1.0,1.08883)))
from_components(Components((0.95047, 1.0, 1.08883)))
}
}

View File

@ -11,7 +11,7 @@
/// automatically used as mutable reference while using this trait.
pub trait AddMut<T> {
type Output = ();
fn add(&mut self, component:T) -> Self::Output;
fn add(&mut self, component: T) -> Self::Output;
}
@ -26,12 +26,12 @@ pub trait AddMut<T> {
/// a current value and first element is a previous one `None` on the first
/// iteration.
#[derive(Debug)]
pub struct CachingIterator<T:Clone, It:Iterator<Item=T>> {
last : Option<T>,
iter : It
pub struct CachingIterator<T: Clone, It: Iterator<Item = T>> {
last: Option<T>,
iter: It,
}
impl<T:Clone, It:Iterator<Item=T>> Iterator for CachingIterator<T, It> {
impl<T: Clone, It: Iterator<Item = T>> Iterator for CachingIterator<T, It> {
type Item = (Option<T>, T);
fn next(&mut self) -> Option<Self::Item> {
@ -47,21 +47,18 @@ impl<T:Clone, It:Iterator<Item=T>> Iterator for CachingIterator<T, It> {
///
/// It is implemented for each iterator over cloneable items.
pub trait IntoCachingIterator {
type Item : Clone;
type Iter : Iterator<Item = Self::Item>;
type Item: Clone;
type Iter: Iterator<Item = Self::Item>;
fn cache_last_value(self) -> CachingIterator<Self::Item,Self::Iter>;
fn cache_last_value(self) -> CachingIterator<Self::Item, Self::Iter>;
}
impl<T : Clone, It : Iterator<Item=T>> IntoCachingIterator for It {
impl<T: Clone, It: Iterator<Item = T>> IntoCachingIterator for It {
type Item = T;
type Iter = Self;
fn cache_last_value(self) -> CachingIterator<Self::Item,Self::Iter> {
CachingIterator {
last : None,
iter : self
}
fn cache_last_value(self) -> CachingIterator<Self::Item, Self::Iter> {
CachingIterator { last: None, iter: self }
}
}
@ -71,18 +68,18 @@ mod tests {
#[test]
fn caching_iterator_on_empty() {
let data = Vec::<i32>::new();
let data = Vec::<i32>::new();
let result = data.iter().cache_last_value().next();
assert_eq!(None, result);
}
#[test]
fn caching_iterator() {
let data = vec![2, 3, 5];
let data = vec![2, 3, 5];
let mut caching_iterator = data.iter().cloned().cache_last_value();
assert_eq!(Some((None ,2)), caching_iterator.next());
assert_eq!(Some((Some(2),3)), caching_iterator.next());
assert_eq!(Some((Some(3),5)), caching_iterator.next());
assert_eq!(None , caching_iterator.next());
assert_eq!(Some((None, 2)), caching_iterator.next());
assert_eq!(Some((Some(2), 3)), caching_iterator.next());
assert_eq!(Some((Some(3), 5)), caching_iterator.next());
assert_eq!(None, caching_iterator.next());
}
}

View File

@ -24,32 +24,60 @@ pub mod traits {
use super::*;
// === Arg ===
pub trait HasArg { type Arg; }
pub trait HasArg {
type Arg;
}
pub type Arg<T> = <T as HasArg>::Arg;
// === Global Operations ===
pub trait HasCheckAll { fn check_all (&self) -> bool; }
pub trait HasUnsetAll { fn unset_all (&mut self); }
pub trait HasCheckAll {
fn check_all(&self) -> bool;
}
pub trait HasUnsetAll {
fn unset_all(&mut self);
}
// === Arity-0 Operations ===
pub trait HasCheck0 { fn check (&self) -> bool; }
pub trait HasSet0 { fn set (&mut self); }
pub trait HasUnset0 { fn unset (&mut self); }
pub trait HasCheck0 {
fn check(&self) -> bool;
}
pub trait HasSet0 {
fn set(&mut self);
}
pub trait HasUnset0 {
fn unset(&mut self);
}
// === Arity-1 Operations ===
pub trait HasCheck1 : HasArg { fn check (& self, arg: &Self::Arg) -> bool; }
pub trait HasSet1 : HasArg { fn set (&mut self, arg: Self::Arg); }
pub trait HasUnset1 : HasArg { fn unset (&mut self, arg: &Self::Arg); }
pub trait HasCheck1: HasArg {
fn check(&self, arg: &Self::Arg) -> bool;
}
pub trait HasSet1: HasArg {
fn set(&mut self, arg: Self::Arg);
}
pub trait HasUnset1: HasArg {
fn unset(&mut self, arg: &Self::Arg);
}
// === Shared Operations ===
pub trait SharedHasUnsetAll { fn unset_all (&self); }
pub trait SharedHasSet0 { fn set (&self); }
pub trait SharedHasUnset0 { fn unset (&self); }
pub trait SharedHasSet1 : HasArg { fn set (&self, arg: Self::Arg); }
pub trait SharedHasUnset1 : HasArg { fn unset (&self, arg:&Self::Arg); }
pub trait SharedHasUnsetAll {
fn unset_all(&self);
}
pub trait SharedHasSet0 {
fn set(&self);
}
pub trait SharedHasUnset0 {
fn unset(&self);
}
pub trait SharedHasSet1: HasArg {
fn set(&self, arg: Self::Arg);
}
pub trait SharedHasUnset1: HasArg {
fn unset(&self, arg: &Self::Arg);
}
// === Type Aliases ===
pub trait DirtyFlagOps = Debug + HasCheckAll + HasUnsetAll;
pub trait DirtyFlagOps = Debug + HasCheckAll + HasUnsetAll;
pub trait DirtyFlagOps0 = DirtyFlagOps + HasCheck0 + HasSet0;
pub trait DirtyFlagOps1 = DirtyFlagOps + HasCheck1 + HasSet1 where Arg<Self>: Debug;
}
@ -68,21 +96,21 @@ pub use traits::*;
/// logging and callback utilities to the underlying data. Moreover, it
/// implements public API for working with dirty flags.
#[derive(Derivative)]
#[derivative(Debug(bound="T:Debug"))]
pub struct DirtyFlag<T,OnMut> {
pub data : T,
logger : Logger,
#[derivative(Debug="ignore")]
on_set : OnMut,
#[derivative(Debug(bound = "T:Debug"))]
pub struct DirtyFlag<T, OnMut> {
pub data: T,
logger: Logger,
#[derivative(Debug = "ignore")]
on_set: OnMut,
}
// === Basics ===
impl<OnMut,T:Default> DirtyFlag<T,OnMut> {
pub fn new(logger:Logger, on_set:OnMut) -> Self {
impl<OnMut, T: Default> DirtyFlag<T, OnMut> {
pub fn new(logger: Logger, on_set: OnMut) -> Self {
let data = default();
Self {data,logger,on_set}
Self { data, logger, on_set }
}
pub fn take(&mut self) -> T {
@ -93,36 +121,35 @@ impl<OnMut,T:Default> DirtyFlag<T,OnMut> {
// === Arguments ===
impl<T:HasArg,OnMut>
HasArg for DirtyFlag<T,OnMut> {
impl<T: HasArg, OnMut> HasArg for DirtyFlag<T, OnMut> {
type Arg = Arg<T>;
}
// === Global Operations ===
impl<T:HasCheckAll,OnMut>
HasCheckAll for DirtyFlag<T,OnMut> {
fn check_all(&self) -> bool { self.data.check_all() }
impl<T: HasCheckAll, OnMut> HasCheckAll for DirtyFlag<T, OnMut> {
fn check_all(&self) -> bool {
self.data.check_all()
}
}
impl<T:HasUnsetAll,OnMut>
HasUnsetAll for DirtyFlag<T,OnMut> {
fn unset_all(&mut self) { self.data.unset_all() }
impl<T: HasUnsetAll, OnMut> HasUnsetAll for DirtyFlag<T, OnMut> {
fn unset_all(&mut self) {
self.data.unset_all()
}
}
// === Check ===
impl<T:DirtyFlagOps0,OnMut>
HasCheck0 for DirtyFlag<T,OnMut> {
impl<T: DirtyFlagOps0, OnMut> HasCheck0 for DirtyFlag<T, OnMut> {
fn check(&self) -> bool {
self.data.check()
}
}
impl<T:DirtyFlagOps1,OnMut>
HasCheck1 for DirtyFlag<T,OnMut> {
impl<T: DirtyFlagOps1, OnMut> HasCheck1 for DirtyFlag<T, OnMut> {
fn check(&self, arg: &Self::Arg) -> bool {
self.data.check(arg)
}
@ -131,8 +158,7 @@ HasCheck1 for DirtyFlag<T,OnMut> {
// === Set ===
impl<T:DirtyFlagOps0,OnMut:FnMut0>
HasSet0 for DirtyFlag<T,OnMut> {
impl<T: DirtyFlagOps0, OnMut: FnMut0> HasSet0 for DirtyFlag<T, OnMut> {
fn set(&mut self) {
let is_set = self.data.check_all();
if !is_set {
@ -144,15 +170,16 @@ HasSet0 for DirtyFlag<T,OnMut> {
}
}
impl<T:DirtyFlagOps1,OnMut:FnMut0>
HasSet1 for DirtyFlag<T,OnMut> {
impl<T: DirtyFlagOps1, OnMut: FnMut0> HasSet1 for DirtyFlag<T, OnMut> {
fn set(&mut self, arg: Self::Arg) {
let first_set = !self.check_all();
let is_set = self.data.check(&arg);
let is_set = self.data.check(&arg);
if !is_set {
self.data.set(arg);
debug!(self.logger, "Setting to {self.data:?}.", || {
if first_set { self.on_set.call(); }
if first_set {
self.on_set.call();
}
})
}
}
@ -161,17 +188,16 @@ HasSet1 for DirtyFlag<T,OnMut> {
// === Unset ===
impl<T:HasUnset0,OnMut>
HasUnset0 for DirtyFlag<T,OnMut> {
impl<T: HasUnset0, OnMut> HasUnset0 for DirtyFlag<T, OnMut> {
fn unset(&mut self) {
info!(self.logger, "Unsetting.");
self.data.unset()
}
}
impl<T:HasUnset1,OnMut>
HasUnset1 for DirtyFlag<T,OnMut>
where Arg<T>:Display {
impl<T: HasUnset1, OnMut> HasUnset1 for DirtyFlag<T, OnMut>
where Arg<T>: Display
{
fn unset(&mut self, arg: &Self::Arg) {
info!(self.logger, "Unsetting {arg}.");
self.data.unset(arg)
@ -189,19 +215,18 @@ HasUnset1 for DirtyFlag<T,OnMut>
/// A version of `DirtyFlag` which uses internal mutability pattern. It is meant to expose the same
/// API but without requiring `self` reference to be mutable.
#[derive(Derivative)]
#[derivative(Debug(bound="T:Debug"))]
#[derivative(Clone(bound=""))]
pub struct SharedDirtyFlag<T,OnMut> {
rc: Rc<RefCell<DirtyFlag<T,OnMut>>>
#[derivative(Debug(bound = "T:Debug"))]
#[derivative(Clone(bound = ""))]
pub struct SharedDirtyFlag<T, OnMut> {
rc: Rc<RefCell<DirtyFlag<T, OnMut>>>,
}
// === API ===
impl<T:Default,OnMut>
SharedDirtyFlag<T,OnMut> {
pub fn new(logger:Logger, on_set:OnMut) -> Self {
Self { rc : Rc::new(RefCell::new(DirtyFlag::new(logger,on_set))) }
impl<T: Default, OnMut> SharedDirtyFlag<T, OnMut> {
pub fn new(logger: Logger, on_set: OnMut) -> Self {
Self { rc: Rc::new(RefCell::new(DirtyFlag::new(logger, on_set))) }
}
pub fn take(&self) -> T {
@ -209,46 +234,41 @@ SharedDirtyFlag<T,OnMut> {
}
}
impl<T,OnMut>
SharedDirtyFlag<T,OnMut> {
impl<T, OnMut> SharedDirtyFlag<T, OnMut> {
pub fn clone_ref(&self) -> Self {
self.clone()
}
}
impl<T,OnMut>
SharedDirtyFlag<T,OnMut> {
pub fn set_callback(&self, on_set:OnMut) {
impl<T, OnMut> SharedDirtyFlag<T, OnMut> {
pub fn set_callback(&self, on_set: OnMut) {
self.rc.borrow_mut().on_set = on_set;
}
}
impl<T,OnMut>
From<Rc<RefCell<DirtyFlag<T,OnMut>>>> for SharedDirtyFlag<T,OnMut> {
fn from(rc: Rc<RefCell<DirtyFlag<T,OnMut>>>) -> Self {
Self {rc}
impl<T, OnMut> From<Rc<RefCell<DirtyFlag<T, OnMut>>>> for SharedDirtyFlag<T, OnMut> {
fn from(rc: Rc<RefCell<DirtyFlag<T, OnMut>>>) -> Self {
Self { rc }
}
}
// === Arg ===
impl<T:HasArg,OnMut> HasArg for SharedDirtyFlag<T,OnMut> {
impl<T: HasArg, OnMut> HasArg for SharedDirtyFlag<T, OnMut> {
type Arg = Arg<T>;
}
// === Global Operations ===
impl<T:HasUnsetAll,OnMut>
SharedHasUnsetAll for SharedDirtyFlag<T,OnMut> {
impl<T: HasUnsetAll, OnMut> SharedHasUnsetAll for SharedDirtyFlag<T, OnMut> {
fn unset_all(&self) {
self.rc.borrow_mut().unset_all()
}
}
impl<T:HasCheckAll,OnMut>
HasCheckAll for SharedDirtyFlag<T,OnMut> {
impl<T: HasCheckAll, OnMut> HasCheckAll for SharedDirtyFlag<T, OnMut> {
fn check_all(&self) -> bool {
self.rc.borrow().check_all()
}
@ -256,40 +276,44 @@ HasCheckAll for SharedDirtyFlag<T,OnMut> {
// === Check ===
impl<T:DirtyFlagOps0,OnMut>
HasCheck0 for SharedDirtyFlag<T,OnMut> {
fn check (&self) -> bool { self.rc.borrow().check() }
impl<T: DirtyFlagOps0, OnMut> HasCheck0 for SharedDirtyFlag<T, OnMut> {
fn check(&self) -> bool {
self.rc.borrow().check()
}
}
impl<T:DirtyFlagOps1,OnMut>
HasCheck1 for SharedDirtyFlag<T,OnMut> {
fn check (&self, arg:&Arg<T>) -> bool { self.rc.borrow().check(arg) }
impl<T: DirtyFlagOps1, OnMut> HasCheck1 for SharedDirtyFlag<T, OnMut> {
fn check(&self, arg: &Arg<T>) -> bool {
self.rc.borrow().check(arg)
}
}
// === Set ===
impl<T:DirtyFlagOps0,OnMut:FnMut0>
SharedHasSet0 for SharedDirtyFlag<T,OnMut> {
fn set (&self) { self.rc.borrow_mut().set() }
impl<T: DirtyFlagOps0, OnMut: FnMut0> SharedHasSet0 for SharedDirtyFlag<T, OnMut> {
fn set(&self) {
self.rc.borrow_mut().set()
}
}
impl<T:DirtyFlagOps1,OnMut:FnMut0>
SharedHasSet1 for SharedDirtyFlag<T,OnMut> {
fn set (&self, arg: Arg<T>) { self.rc.borrow_mut().set(arg) }
impl<T: DirtyFlagOps1, OnMut: FnMut0> SharedHasSet1 for SharedDirtyFlag<T, OnMut> {
fn set(&self, arg: Arg<T>) {
self.rc.borrow_mut().set(arg)
}
}
// === Unset ===
impl<T:HasUnset0,OnMut>
SharedHasUnset0 for SharedDirtyFlag<T,OnMut> {
impl<T: HasUnset0, OnMut> SharedHasUnset0 for SharedDirtyFlag<T, OnMut> {
fn unset(&self) {
self.rc.borrow_mut().unset()
}
}
impl<T:HasUnset1,OnMut>
SharedHasUnset1 for SharedDirtyFlag<T,OnMut> where Arg<T>:Display {
fn unset(&self, arg:&Self::Arg) {
impl<T: HasUnset1, OnMut> SharedHasUnset1 for SharedDirtyFlag<T, OnMut>
where Arg<T>: Display
{
fn unset(&self, arg: &Self::Arg) {
self.rc.borrow_mut().unset(arg)
}
}
@ -307,17 +331,39 @@ SharedHasUnset1 for SharedDirtyFlag<T,OnMut> where Arg<T>:Display {
/// The on / off dirty flag. If you need a simple dirty / clean switch, this one
/// is the right choice.
pub type Bool <OnMut=()> = DirtyFlag <BoolData,OnMut>;
pub type SharedBool <OnMut=()> = SharedDirtyFlag <BoolData,OnMut>;
pub trait BoolCtx <OnMut> = where OnMut:FnMut0;
pub type Bool<OnMut = ()> = DirtyFlag<BoolData, OnMut>;
pub type SharedBool<OnMut = ()> = SharedDirtyFlag<BoolData, OnMut>;
pub trait BoolCtx<OnMut> = where OnMut: FnMut0;
#[derive(Clone,Copy,Debug,Display,Default)]
pub struct BoolData { is_dirty: bool }
impl HasCheckAll for BoolData { fn check_all (&self) -> bool { self.is_dirty } }
impl HasUnsetAll for BoolData { fn unset_all (&mut self) { self.is_dirty = false } }
impl HasCheck0 for BoolData { fn check (& self) -> bool { self.is_dirty } }
impl HasSet0 for BoolData { fn set (&mut self) { self.is_dirty = true } }
impl HasUnset0 for BoolData { fn unset (&mut self) { self.is_dirty = false } }
#[derive(Clone, Copy, Debug, Display, Default)]
pub struct BoolData {
is_dirty: bool,
}
impl HasCheckAll for BoolData {
fn check_all(&self) -> bool {
self.is_dirty
}
}
impl HasUnsetAll for BoolData {
fn unset_all(&mut self) {
self.is_dirty = false
}
}
impl HasCheck0 for BoolData {
fn check(&self) -> bool {
self.is_dirty
}
}
impl HasSet0 for BoolData {
fn set(&mut self) {
self.is_dirty = true
}
}
impl HasUnset0 for BoolData {
fn unset(&mut self) {
self.is_dirty = false
}
}
@ -328,46 +374,65 @@ impl HasUnset0 for BoolData { fn unset (&mut self) { self.is_dirty
/// Dirty flag which keeps information about a range of dirty items. It does not track items
/// separately, nor you are allowed to keep multiple ranges in it. Just a single value range.
pub type Range <Ix,OnMut> = DirtyFlag <RangeData<Ix>,OnMut>;
pub type SharedRange <Ix,OnMut> = SharedDirtyFlag <RangeData<Ix>,OnMut>;
pub trait RangeCtx <OnMut> = where OnMut:FnMut0;
pub trait RangeIx = PartialOrd + Copy + Debug;
pub type Range<Ix, OnMut> = DirtyFlag<RangeData<Ix>, OnMut>;
pub type SharedRange<Ix, OnMut> = SharedDirtyFlag<RangeData<Ix>, OnMut>;
pub trait RangeCtx<OnMut> = where OnMut: FnMut0;
pub trait RangeIx = PartialOrd + Copy + Debug;
#[derive(Debug,Default)]
pub struct RangeData<Ix=usize> { pub range: Option<RangeInclusive<Ix>> }
#[derive(Debug, Default)]
pub struct RangeData<Ix = usize> {
pub range: Option<RangeInclusive<Ix>>,
}
impl<Ix> HasArg for RangeData<Ix> { type Arg = Ix; }
impl<Ix> HasCheckAll for RangeData<Ix> { fn check_all(&self) -> bool { self.range.is_some() } }
impl<Ix> HasUnsetAll for RangeData<Ix> { fn unset_all(&mut self) { self.range = None } }
impl<Ix> HasArg for RangeData<Ix> {
type Arg = Ix;
}
impl<Ix> HasCheckAll for RangeData<Ix> {
fn check_all(&self) -> bool {
self.range.is_some()
}
}
impl<Ix> HasUnsetAll for RangeData<Ix> {
fn unset_all(&mut self) {
self.range = None
}
}
impl<Ix:RangeIx> HasCheck1 for RangeData<Ix> {
fn check(&self, ix:&Ix) -> bool {
impl<Ix: RangeIx> HasCheck1 for RangeData<Ix> {
fn check(&self, ix: &Ix) -> bool {
self.range.as_ref().map(|r| r.contains(ix)) == Some(true)
}
}
impl<Ix:RangeIx> HasSet1 for RangeData<Ix> {
fn set(&mut self, ix:Ix) {
impl<Ix: RangeIx> HasSet1 for RangeData<Ix> {
fn set(&mut self, ix: Ix) {
self.range = match &self.range {
None => Some(ix ..= ix),
Some(r) => {
if ix < *r.start() { Some (ix ..= *r.end()) }
else if ix > *r.end() { Some (*r.start() ..= ix) }
else { Some (r.clone()) }
}
None => Some(ix..=ix),
Some(r) =>
if ix < *r.start() {
Some(ix..=*r.end())
} else if ix > *r.end() {
Some(*r.start()..=ix)
} else {
Some(r.clone())
},
};
}
}
impl<Ix:RangeIx> HasUnset1 for RangeData<Ix> {
fn unset(&mut self, _arg:&Self::Arg) {}
impl<Ix: RangeIx> HasUnset1 for RangeData<Ix> {
fn unset(&mut self, _arg: &Self::Arg) {}
}
impl<Ix:RangeIx> Display for RangeData<Ix> {
fn fmt(&self, f:&mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.range.as_ref().map(|t|
format!("[{:?}...{:?}]",t.start(),t.end()))
.unwrap_or_else(|| "false".into())
impl<Ix: RangeIx> Display for RangeData<Ix> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
self.range
.as_ref()
.map(|t| format!("[{:?}...{:?}]", t.start(), t.end()))
.unwrap_or_else(|| "false".into())
)
}
}
@ -382,45 +447,57 @@ impl<Ix:RangeIx> Display for RangeData<Ix> {
/// counterpart. Please note that it uses `FxHashSet` under the hood, so there
/// are no guarantees regarding attack-proof hashing algorithm here.
pub type Set <Ix,OnMut=()> = DirtyFlag <SetData<Ix>,OnMut>;
pub type SharedSet <Ix,OnMut=()> = SharedDirtyFlag <SetData<Ix>,OnMut>;
pub trait SetCtx <OnMut> = where OnMut:FnMut0;
pub trait SetItem = Eq + Hash + Debug;
pub type Set<Ix, OnMut = ()> = DirtyFlag<SetData<Ix>, OnMut>;
pub type SharedSet<Ix, OnMut = ()> = SharedDirtyFlag<SetData<Ix>, OnMut>;
pub trait SetCtx<OnMut> = where OnMut: FnMut0;
pub trait SetItem = Eq + Hash + Debug;
#[derive(Derivative,Shrinkwrap)]
#[derivative(Debug (bound="Item:SetItem"))]
#[derivative(Default (bound="Item:SetItem"))]
pub struct SetData<Item> { pub set: FxHashSet<Item> }
#[derive(Derivative, Shrinkwrap)]
#[derivative(Debug(bound = "Item:SetItem"))]
#[derivative(Default(bound = "Item:SetItem"))]
pub struct SetData<Item> {
pub set: FxHashSet<Item>,
}
impl<Item> HasArg for SetData<Item> { type Arg = Item; }
impl<Item> HasCheckAll for SetData<Item> { fn check_all(&self) -> bool { !self.set.is_empty() } }
impl<Item> HasUnsetAll for SetData<Item> { fn unset_all(&mut self) { self.set.clear(); } }
impl<Item> HasArg for SetData<Item> {
type Arg = Item;
}
impl<Item> HasCheckAll for SetData<Item> {
fn check_all(&self) -> bool {
!self.set.is_empty()
}
}
impl<Item> HasUnsetAll for SetData<Item> {
fn unset_all(&mut self) {
self.set.clear();
}
}
impl<Item:SetItem> HasCheck1 for SetData<Item> {
fn check(&self, a:&Item) -> bool {
impl<Item: SetItem> HasCheck1 for SetData<Item> {
fn check(&self, a: &Item) -> bool {
self.set.contains(a)
}
}
impl<Item:SetItem> HasSet1 for SetData<Item> {
fn set (&mut self, a:Item) {
impl<Item: SetItem> HasSet1 for SetData<Item> {
fn set(&mut self, a: Item) {
self.set.insert(a);
}
}
impl<Item:SetItem> HasUnset1 for SetData<Item> {
fn unset (&mut self, a:&Item) {
impl<Item: SetItem> HasUnset1 for SetData<Item> {
fn unset(&mut self, a: &Item) {
self.set.remove(a);
}
}
impl<Item:SetItem> Display for SetData<Item> {
impl<Item: SetItem> Display for SetData<Item> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,"{:?}",self.set)
write!(f, "{:?}", self.set)
}
}
impl<'t,Item:SetItem> IntoIterator for &'t SetData<Item> {
impl<'t, Item: SetItem> IntoIterator for &'t SetData<Item> {
type Item = &'t Item;
type IntoIter = <&'t FxHashSet<Item> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
@ -435,43 +512,55 @@ impl<'t,Item:SetItem> IntoIterator for &'t SetData<Item> {
// ==============
/// Dirty flag which keeps a vector of dirty values.
pub type Vector <Item,OnMut=()> = DirtyFlag <VectorData<Item>,OnMut>;
pub type SharedVector <Item,OnMut=()> = SharedDirtyFlag <VectorData<Item>,OnMut>;
pub trait VectorItem = Debug + PartialEq;
pub type Vector<Item, OnMut = ()> = DirtyFlag<VectorData<Item>, OnMut>;
pub type SharedVector<Item, OnMut = ()> = SharedDirtyFlag<VectorData<Item>, OnMut>;
pub trait VectorItem = Debug + PartialEq;
#[derive(Derivative,Debug,Shrinkwrap)]
#[derivative(Default (bound=""))]
pub struct VectorData<Item> { pub vec: Vec<Item> }
#[derive(Derivative, Debug, Shrinkwrap)]
#[derivative(Default(bound = ""))]
pub struct VectorData<Item> {
pub vec: Vec<Item>,
}
impl<Item> HasArg for VectorData<Item> { type Arg = Item; }
impl<Item> HasCheckAll for VectorData<Item> { fn check_all(&self) -> bool { !self.vec.is_empty() } }
impl<Item> HasUnsetAll for VectorData<Item> { fn unset_all(&mut self) { self.vec.clear(); } }
impl<Item> HasArg for VectorData<Item> {
type Arg = Item;
}
impl<Item> HasCheckAll for VectorData<Item> {
fn check_all(&self) -> bool {
!self.vec.is_empty()
}
}
impl<Item> HasUnsetAll for VectorData<Item> {
fn unset_all(&mut self) {
self.vec.clear();
}
}
impl<Item:PartialEq> HasCheck1 for VectorData<Item> {
fn check(&self, a:&Item) -> bool {
impl<Item: PartialEq> HasCheck1 for VectorData<Item> {
fn check(&self, a: &Item) -> bool {
self.vec.contains(a)
}
}
impl<Item> HasSet1 for VectorData<Item> {
fn set (&mut self, a:Item) {
fn set(&mut self, a: Item) {
self.vec.push(a);
}
}
impl<Item:PartialEq> HasUnset1 for VectorData<Item> {
fn unset (&mut self, a:&Item) {
impl<Item: PartialEq> HasUnset1 for VectorData<Item> {
fn unset(&mut self, a: &Item) {
self.vec.remove_item(a);
}
}
impl<Item:Debug> Display for VectorData<Item> {
impl<Item: Debug> Display for VectorData<Item> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,"{:?}",self.vec)
write!(f, "{:?}", self.vec)
}
}
impl<'t,Item> IntoIterator for &'t VectorData<Item> {
impl<'t, Item> IntoIterator for &'t VectorData<Item> {
type Item = &'t Item;
type IntoIter = <&'t Vec<Item> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
@ -491,61 +580,61 @@ use bit_field::BitField as BF;
/// items must be a plain enumerator implementing `Into<usize>`. The data is
/// stored as an efficient `BitField` under the hood.
pub type Enum <Prim,T,OnMut> = DirtyFlag <EnumData<Prim,T>,OnMut>;
pub type SharedEnum <Prim,T,OnMut> = SharedDirtyFlag <EnumData<Prim,T>,OnMut>;
pub trait EnumCtx <OnMut> = where OnMut:FnMut0;
pub trait EnumBase = Default + PartialEq + Copy + BF;
pub trait EnumElem = Copy+Into<usize>;
pub type Enum<Prim, T, OnMut> = DirtyFlag<EnumData<Prim, T>, OnMut>;
pub type SharedEnum<Prim, T, OnMut> = SharedDirtyFlag<EnumData<Prim, T>, OnMut>;
pub trait EnumCtx<OnMut> = where OnMut: FnMut0;
pub trait EnumBase = Default + PartialEq + Copy + BF;
pub trait EnumElem = Copy + Into<usize>;
/// Dirty flag which keeps dirty indexes in a `BitField` under the hood.
pub type BitField <Prim,OnMut> = Enum <Prim,usize,OnMut>;
pub type SharedBitField <Prim,OnMut> = SharedEnum <Prim,usize,OnMut>;
pub type BitField<Prim, OnMut> = Enum<Prim, usize, OnMut>;
pub type SharedBitField<Prim, OnMut> = SharedEnum<Prim, usize, OnMut>;
#[derive(Derivative)]
#[derivative(Debug(bound="Prim:Debug"))]
#[derivative(Default(bound="Prim:Default"))]
pub struct EnumData<Prim=u32,T=usize> {
pub bits : Prim,
phantom : PhantomData<T>
#[derivative(Debug(bound = "Prim:Debug"))]
#[derivative(Default(bound = "Prim:Default"))]
pub struct EnumData<Prim = u32, T = usize> {
pub bits: Prim,
phantom: PhantomData<T>,
}
impl<Prim,T> HasArg for EnumData<Prim,T> {
impl<Prim, T> HasArg for EnumData<Prim, T> {
type Arg = T;
}
impl<Prim:EnumBase,T> HasCheckAll for EnumData<Prim,T> {
impl<Prim: EnumBase, T> HasCheckAll for EnumData<Prim, T> {
fn check_all(&self) -> bool {
self.bits != default()
}
}
impl<Prim:EnumBase,T> HasUnsetAll for EnumData<Prim,T> {
impl<Prim: EnumBase, T> HasUnsetAll for EnumData<Prim, T> {
fn unset_all(&mut self) {
self.bits = default()
}
}
impl<Prim:EnumBase,T:EnumElem> HasCheck1 for EnumData<Prim,T> {
fn check(&self, t:&T) -> bool {
impl<Prim: EnumBase, T: EnumElem> HasCheck1 for EnumData<Prim, T> {
fn check(&self, t: &T) -> bool {
self.bits.get_bit((*t).into())
}
}
impl<Prim:EnumBase,T:EnumElem> HasSet1 for EnumData<Prim,T> {
fn set(&mut self, t:T) {
impl<Prim: EnumBase, T: EnumElem> HasSet1 for EnumData<Prim, T> {
fn set(&mut self, t: T) {
self.bits.set_bit(t.into(), true);
}
}
impl<Prim:EnumBase,T:EnumElem> HasUnset1 for EnumData<Prim,T> {
fn unset(&mut self, t:&T) {
impl<Prim: EnumBase, T: EnumElem> HasUnset1 for EnumData<Prim, T> {
fn unset(&mut self, t: &T) {
self.bits.set_bit((*t).into(), false);
}
}
impl<Prim:EnumBase,T:EnumElem> Display for EnumData<Prim,T> {
impl<Prim: EnumBase, T: EnumElem> Display for EnumData<Prim, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,"{}",self.check_all())
write!(f, "{}", self.check_all())
}
}

View File

@ -30,36 +30,37 @@
/// For the input of `[mut] FnMut FnMut1<T1>`, the following code will be generated:
///
/// ```
/// pub trait FnMut1<T1> {
/// type Output;
/// fn call(&mut self, T1: T1) -> Self::Output;
/// }
/// pub trait FnMut1<T1> {
/// type Output;
/// fn call(&mut self, T1: T1) -> Self::Output;
/// }
///
/// impl<T1> FnMut1<T1> for () {
/// type Output = ();
/// fn call(&mut self, _: T1) {}
/// }
/// impl<T1> FnMut1<T1> for () {
/// type Output = ();
/// fn call(&mut self, _: T1) {}
/// }
///
/// impl<T,T1> FnMut1<T1> for Option<T>
/// where T: FnMut1<T1> {
/// type Output = Option<T::Output>;
/// fn call(&mut self, t1:T1) -> Self::Output {
/// match self {
/// Some(f) => Some(f.call(t1)),
/// None => None,
/// }
/// impl<T, T1> FnMut1<T1> for Option<T>
/// where T: FnMut1<T1>
/// {
/// type Output = Option<T::Output>;
/// fn call(&mut self, t1: T1) -> Self::Output {
/// match self {
/// Some(f) => Some(f.call(t1)),
/// None => None,
/// }
/// }
/// }
///
/// impl<F,T,T1> FnMut1<T1> for F
/// where F: FnMut(T1) -> T {
/// type Output = T;
/// fn call(&mut self, t1:T1) -> Self::Output {
/// self(t1)
/// }
/// impl<F, T, T1> FnMut1<T1> for F
/// where F: FnMut(T1) -> T
/// {
/// type Output = T;
/// fn call(&mut self, t1: T1) -> Self::Output {
/// self(t1)
/// }
/// }
/// ```
///
macro_rules! define_fn {
($( [$($mut:ident)?] $fn_name:ident $name:ident $(<$($arg:ident),*>)?; )*) => {$(

View File

@ -13,21 +13,24 @@ use crate::prelude::*;
/// Strongly typed value representation in the mix space.
#[allow(missing_docs)]
pub struct Space<T:Mixable> {
pub value : Repr<T>
pub struct Space<T: Mixable> {
pub value: Repr<T>,
}
impl<T:Mixable> Space<T> {
impl<T: Mixable> Space<T> {
/// Constructor.
pub fn new(value:Repr<T>) -> Self {
Self {value}
pub fn new(value: Repr<T>) -> Self {
Self { value }
}
}
impl<T:Mixable> Debug for Space<T>
where T:Mixable, Repr<T>:Debug {
fn fmt(&self, f:&mut fmt::Formatter<'_>) -> fmt::Result {
write!(f,"Space({:?})",self.value)
impl<T: Mixable> Debug for Space<T>
where
T: Mixable,
Repr<T>: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Space({:?})", self.value)
}
}
@ -40,34 +43,34 @@ impl<T:Mixable> Debug for Space<T>
/// Type association between a value and its representation used for mixing. For example, for colors
/// in sRGB space, the representation may be RGB in the linear color space.
#[allow(missing_docs)]
pub trait Mixable : BiInto<Space<Self>> {
type Repr : Value;
pub trait Mixable: BiInto<Space<Self>> {
type Repr: Value;
}
/// Mixable::Repr getter.
pub type Repr<T> = <T as Mixable>::Repr;
/// Trait for values that can be mixed.
pub trait Value = Sized + Mul<f32,Output=Self> + Add<Output=Self>;
pub trait Value = Sized + Mul<f32, Output = Self> + Add<Output = Self>;
// === Utils ===
/// Convert the mix space representation to corresponding value.
pub fn from_space<T:Mixable>(value:Repr<T>) -> T {
Space{value}.into()
pub fn from_space<T: Mixable>(value: Repr<T>) -> T {
Space { value }.into()
}
/// Convert value to corresponding mix space representation.
pub fn into_space<T:Mixable>(t:T) -> Repr<T> {
pub fn into_space<T: Mixable>(t: T) -> Repr<T> {
t.into().value
}
/// Perform a mix of two values. See module docs to learn more.
pub fn mix<T:Mixable>(t1:T, t2:T, coefficient:f32) -> T {
pub fn mix<T: Mixable>(t1: T, t2: T, coefficient: f32) -> T {
let v1 = into_space(t1);
let v2 = into_space(t2);
let v = v1 * (1.0 - coefficient) + v2 * coefficient;
let v = v1 * (1.0 - coefficient) + v2 * coefficient;
from_space(v)
}
@ -99,4 +102,4 @@ macro_rules! define_self_mixables {
)*}
}
define_self_mixables!(f32,Vector2,Vector3,Vector4);
define_self_mixables!(f32, Vector2, Vector3, Vector4);

View File

@ -1,8 +1,8 @@
#![allow(missing_docs)]
use crate::prelude::*;
use crate::data::function::traits::FnMut0;
use crate::data::function::traits::FnMut1;
use crate::prelude::*;
@ -14,46 +14,44 @@ use crate::data::function::traits::FnMut1;
/// structure changes.
#[derive(Shrinkwrap)]
#[derive(Derivative)]
#[derivative(Clone,Debug(bound="T:Debug"))]
pub struct Observable<T,OnMut,OnResize> {
#[derivative(Clone, Debug(bound = "T:Debug"))]
pub struct Observable<T, OnMut, OnResize> {
#[shrinkwrap(main_field)]
pub data: T,
#[derivative(Debug="ignore")]
pub on_mut: OnMut,
#[derivative(Debug="ignore")]
pub data: T,
#[derivative(Debug = "ignore")]
pub on_mut: OnMut,
#[derivative(Debug = "ignore")]
pub on_resize: OnResize,
}
impl<T:Default,OnMut,OnResize>
Observable<T,OnMut,OnResize> {
pub fn new(on_mut:OnMut, on_resize:OnResize) -> Self {
impl<T: Default, OnMut, OnResize> Observable<T, OnMut, OnResize> {
pub fn new(on_mut: OnMut, on_resize: OnResize) -> Self {
let data = default();
Self {data,on_mut,on_resize}
Self { data, on_mut, on_resize }
}
}
impl<T:Index<Ix>,OnMut,OnResize,Ix>
Index<Ix> for Observable<T,OnMut,OnResize> {
impl<T: Index<Ix>, OnMut, OnResize, Ix> Index<Ix> for Observable<T, OnMut, OnResize> {
type Output = <T as Index<Ix>>::Output;
#[inline]
fn index(&self, index:Ix) -> &Self::Output {
fn index(&self, index: Ix) -> &Self::Output {
&self.data[index]
}
}
impl<T:IndexMut<Ix>, OnMut: FnMut1<Ix> ,OnResize, Ix:Copy>
IndexMut<Ix> for Observable<T,OnMut,OnResize> {
impl<T: IndexMut<Ix>, OnMut: FnMut1<Ix>, OnResize, Ix: Copy> IndexMut<Ix>
for Observable<T, OnMut, OnResize>
{
#[inline]
fn index_mut(&mut self, index:Ix) -> &mut Self::Output {
fn index_mut(&mut self, index: Ix) -> &mut Self::Output {
self.on_mut.call(index);
&mut self.data[index]
}
}
impl <T:Extend<S>,S,OnMut,OnResize:FnMut0>
Extend<S> for Observable<T,OnMut,OnResize> {
impl<T: Extend<S>, S, OnMut, OnResize: FnMut0> Extend<S> for Observable<T, OnMut, OnResize> {
#[inline]
fn extend<I:IntoIterator<Item=S>>(&mut self, iter:I) {
fn extend<I: IntoIterator<Item = S>>(&mut self, iter: I) {
self.on_resize.call();
self.data.extend(iter)
}

File diff suppressed because it is too large Load Diff

View File

@ -11,15 +11,15 @@ use crate::prelude::*;
// =============
/// Structure containing all the gathered stats.
#[derive(Debug,Clone,CloneRef)]
#[derive(Debug, Clone, CloneRef)]
pub struct Stats {
rc: Rc<RefCell<StatsData>>
rc: Rc<RefCell<StatsData>>,
}
impl Default for Stats {
fn default() -> Self {
let rc = Rc::new(RefCell::new(default()));
Self {rc}
Self { rc }
}
}
@ -70,7 +70,7 @@ macro_rules! gen_stats {
}};
}
gen_stats!{
gen_stats! {
gpu_memory_usage : u32,
draw_call_count : usize,
buffer_count : usize,
@ -86,10 +86,10 @@ gen_stats!{
impl StatsData {
fn reset_per_frame_statistics(&mut self) {
self.draw_call_count = 0;
self.draw_call_count = 0;
self.shader_compile_count = 0;
self.data_upload_count = 0;
self.data_upload_size = 0;
self.data_upload_count = 0;
self.data_upload_size = 0;
}
}

View File

@ -3,13 +3,13 @@
pub mod camera;
pub mod layout;
pub mod object;
pub mod navigation;
pub mod object;
pub mod render;
pub mod scene;
pub mod shape;
pub mod symbol;
pub mod style;
pub mod symbol;
pub mod world;
@ -26,11 +26,11 @@ pub mod traits {
/// Common types.
pub mod types {
use super::*;
pub use object::Object;
pub use scene::Scene;
pub use scene::dom::DomScene;
pub use super::symbol::*;
pub use super::traits::*;
use super::*;
pub use object::Object;
pub use scene::dom::DomScene;
pub use scene::Scene;
}
pub use types::*;

View File

@ -3,11 +3,11 @@
use crate::prelude::*;
use crate::control::callback;
use crate::data::dirty;
use crate::data::dirty::traits::*;
use crate::display;
use crate::display::scene::Scene;
use crate::data::dirty::traits::*;
use crate::control::callback;
use nalgebra::Perspective3;
@ -18,11 +18,11 @@ use nalgebra::Perspective3;
// ==============
/// Camera's frustum screen dimensions.
#[derive(Clone,Copy,Debug,Default)]
#[derive(Clone, Copy, Debug, Default)]
#[allow(missing_docs)]
pub struct Screen {
pub width : f32,
pub height : f32,
pub width: f32,
pub height: f32,
}
impl Screen {
@ -49,22 +49,22 @@ impl Screen {
// ==================
/// Camera's projection type.
#[derive(Clone,Copy,Debug)]
#[derive(Clone, Copy, Debug)]
pub enum Projection {
/// Perspective projection.
Perspective {
/// Field of view.
fov : f32
fov: f32,
},
/// Orthographic projection.
Orthographic
Orthographic,
}
impl Default for Projection {
fn default() -> Self {
let fov = 45.0f32.to_radians();
Self::Perspective {fov}
Self::Perspective { fov }
}
}
@ -75,11 +75,11 @@ impl Default for Projection {
// ================
/// Camera's frustum clipping range.
#[derive(Clone,Copy,Debug)]
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub struct Clipping {
pub near : f32,
pub far : f32
pub near: f32,
pub far: f32,
}
impl Default for Clipping {
@ -87,8 +87,8 @@ impl Default for Clipping {
let near = 1.0;
//FIXME: Bigger screens needs bigger far values, which means that this value has to be
// updated when the screen is resized.
let far = 10000.0;
Self {near,far}
let far = 10000.0;
Self { near, far }
}
}
@ -99,17 +99,17 @@ impl Default for Clipping {
// =============
/// Dirty status of camera properties.
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
pub struct Dirty {
projection : ProjectionDirty,
transform : TransformDirty
projection: ProjectionDirty,
transform: TransformDirty,
}
impl Dirty {
fn new(logger:&Logger) -> Self {
let projection = ProjectionDirty::new(Logger::new_sub(&logger,"projection"),());
let transform = TransformDirty::new(Logger::new_sub(&logger,"transform"),());
Self {projection,transform}
fn new(logger: &Logger) -> Self {
let projection = ProjectionDirty::new(Logger::new_sub(&logger, "projection"), ());
let transform = TransformDirty::new(Logger::new_sub(&logger, "transform"), ());
Self { projection, transform }
}
}
@ -123,19 +123,19 @@ impl Dirty {
#[derive(Debug)]
#[allow(missing_copy_implementations)]
pub struct Matrix {
view : Matrix4<f32>,
view_inversed : Matrix4<f32>,
projection : Matrix4<f32>,
view_projection : Matrix4<f32>,
view: Matrix4<f32>,
view_inversed: Matrix4<f32>,
projection: Matrix4<f32>,
view_projection: Matrix4<f32>,
}
impl Matrix {
fn new() -> Self {
let view = Matrix4::identity();
let view_inversed = Matrix4::identity();
let projection = Matrix4::identity();
let view = Matrix4::identity();
let view_inversed = Matrix4::identity();
let projection = Matrix4::identity();
let view_projection = Matrix4::identity();
Self {view,view_inversed,projection,view_projection}
Self { view, view_inversed, projection, view_projection }
}
}
@ -160,50 +160,61 @@ pub trait ZoomUpdateFn = callback::CallbackMut1Fn<f32>;
/// Internal `Camera2d` representation. Please see `Camera2d` for full documentation.
#[derive(Debug)]
struct Camera2dData {
display_object : display::object::Instance,
screen : Screen,
zoom : f32,
z_zoom_1 : f32,
projection : Projection,
clipping : Clipping,
matrix : Matrix,
dirty : Dirty,
zoom_update_registry : callback::Registry1<f32>,
screen_update_registry : callback::Registry1<Vector2<f32>>,
display_object: display::object::Instance,
screen: Screen,
zoom: f32,
z_zoom_1: f32,
projection: Projection,
clipping: Clipping,
matrix: Matrix,
dirty: Dirty,
zoom_update_registry: callback::Registry1<f32>,
screen_update_registry: callback::Registry1<Vector2<f32>>,
}
type ProjectionDirty = dirty::SharedBool<()>;
type TransformDirty = dirty::SharedBool<()>;
type TransformDirty = dirty::SharedBool<()>;
impl Camera2dData {
fn new(logger:Logger, display_object:&display::object::Instance) -> Self {
let screen = Screen::new();
let projection = default();
let clipping = default();
let zoom = 1.0;
let z_zoom_1 = 1.0;
let matrix = default();
let dirty = Dirty::new(&Logger::new_sub(&logger,"dirty"));
let display_object = display_object.clone_ref();
let zoom_update_registry = default();
fn new(logger: Logger, display_object: &display::object::Instance) -> Self {
let screen = Screen::new();
let projection = default();
let clipping = default();
let zoom = 1.0;
let z_zoom_1 = 1.0;
let matrix = default();
let dirty = Dirty::new(&Logger::new_sub(&logger, "dirty"));
let display_object = display_object.clone_ref();
let zoom_update_registry = default();
let screen_update_registry = default();
display_object.set_on_updated(f_!(dirty.transform.set()));
display_object.mod_position(|p| p.z = 1.0);
dirty.projection.set();
Self {display_object,screen,zoom,z_zoom_1,projection,clipping,matrix,dirty
,zoom_update_registry,screen_update_registry}.init()
Self {
display_object,
screen,
zoom,
z_zoom_1,
projection,
clipping,
matrix,
dirty,
zoom_update_registry,
screen_update_registry,
}
.init()
}
fn init(mut self) -> Self {
self.set_screen(self.screen.width,self.screen.height);
self.set_screen(self.screen.width, self.screen.height);
self
}
fn add_zoom_update_callback<F:ZoomUpdateFn>(&mut self, f:F) -> callback::Handle {
fn add_zoom_update_callback<F: ZoomUpdateFn>(&mut self, f: F) -> callback::Handle {
self.zoom_update_registry.add(f)
}
fn add_screen_update_callback<F:ScreenUpdateFn>(&mut self, f:F) -> callback::Handle {
fn add_screen_update_callback<F: ScreenUpdateFn>(&mut self, f: F) -> callback::Handle {
self.screen_update_registry.add(f)
}
@ -215,21 +226,21 @@ impl Camera2dData {
fn recompute_projection_matrix(&mut self) {
self.matrix.projection = match &self.projection {
Projection::Perspective {fov} => {
Projection::Perspective { fov } => {
let aspect = self.screen.aspect();
let near = self.clipping.near;
let far = self.clipping.far;
*Perspective3::new(aspect,*fov,near,far).as_matrix()
let near = self.clipping.near;
let far = self.clipping.far;
*Perspective3::new(aspect, *fov, near, far).as_matrix()
}
_ => unimplemented!()
_ => unimplemented!(),
};
}
fn inversed_projection_matrix(&self) -> Matrix4<f32> {
match &self.projection {
Projection::Perspective {..} =>
Projection::Perspective { .. } =>
Perspective3::from_matrix_unchecked(self.matrix.projection).inverse(),
_ => unimplemented!()
_ => unimplemented!(),
}
}
@ -239,7 +250,7 @@ impl Camera2dData {
// https://github.com/rust-lang/rust-clippy/issues/4914
#[allow(clippy::useless_let_if_seq)]
fn update(&mut self, scene:&Scene) -> bool {
fn update(&mut self, scene: &Scene) -> bool {
self.display_object.update(scene);
let mut changed = false;
if self.dirty.transform.check() {
@ -275,31 +286,31 @@ impl Camera2dData {
&mut self.clipping
}
fn set_screen(&mut self, width:f32, height:f32) {
fn set_screen(&mut self, width: f32, height: f32) {
if self.screen.is_degenerated() {
self.zoom = 1.0;
}
self.screen.width = width;
self.screen.width = width;
self.screen.height = height;
self.dirty.projection.set();
match &self.projection {
Projection::Perspective {fov} => {
let zoom = self.zoom;
let alpha = fov / 2.0;
let z_zoom_1 = height / (2.0 * alpha.tan());
Projection::Perspective { fov } => {
let zoom = self.zoom;
let alpha = fov / 2.0;
let z_zoom_1 = height / (2.0 * alpha.tan());
self.z_zoom_1 = z_zoom_1;
self.mod_position_keep_zoom(|t| t.z = z_zoom_1 / zoom);
}
_ => unimplemented!()
_ => unimplemented!(),
};
let dimensions = Vector2::new(width,height);
let dimensions = Vector2::new(width, height);
self.screen_update_registry.run_all(&dimensions);
}
fn reset_zoom(&mut self) {
self.zoom = 1.0;
self.set_screen(self.screen.width,self.screen.height);
self.set_screen(self.screen.width, self.screen.height);
}
/// Check whether the screen size is zero or negative.
@ -312,21 +323,21 @@ impl Camera2dData {
// === Transform Setters ===
impl Camera2dData {
fn mod_position<F:FnOnce(&mut Vector3<f32>)>(&mut self, f:F) {
fn mod_position<F: FnOnce(&mut Vector3<f32>)>(&mut self, f: F) {
self.mod_position_keep_zoom(f);
let z = self.display_object.position().z.abs();
let z = self.display_object.position().z.abs();
self.zoom = if z < std::f32::EPSILON { std::f32::INFINITY } else { self.z_zoom_1 / z };
}
fn set_position(&mut self, value:Vector3<f32>) {
fn set_position(&mut self, value: Vector3<f32>) {
self.mod_position(|p| *p = value);
}
fn set_rotation(&mut self, yaw:f32, pitch:f32, roll:f32) {
self.display_object.mod_rotation(|r| *r = Vector3::new(yaw,pitch,roll))
fn set_rotation(&mut self, yaw: f32, pitch: f32, roll: f32) {
self.display_object.mod_rotation(|r| *r = Vector3::new(yaw, pitch, roll))
}
fn mod_position_keep_zoom<F:FnOnce(&mut Vector3<f32>)>(&mut self, f:F) {
fn mod_position_keep_zoom<F: FnOnce(&mut Vector3<f32>)>(&mut self, f: F) {
self.display_object.mod_position(f)
}
}
@ -350,23 +361,23 @@ impl Camera2dData {
/// drawing elements and scaling the view. By default, the `alignment` is set to center, which
/// defines the origin center at the center of the screen. When scaling the view, objects placed
/// in the center of the view will not move visually. If you set the alignment to bottom-left
/// corner, you will get a view which behaves like a window in window-based GUIs. When scaling
/// the window, the left-bottom corner will stay in place.
#[derive(Clone,CloneRef,Debug)]
/// corner, you will get a view which behaves like a window in window-based GUIs. When scaling the
/// window, the left-bottom corner will stay in place.
#[derive(Clone, CloneRef, Debug)]
pub struct Camera2d {
display_object : display::object::Instance,
data : Rc<RefCell<Camera2dData>>,
display_object: display::object::Instance,
data: Rc<RefCell<Camera2dData>>,
}
impl Camera2d {
/// Creates new [`Camera2d`] instance. Please note that the camera will be of zero-size and in
/// order for it to work properly, you have to initialize it by using the `set_screen` method.
pub fn new(logger:impl AnyLogger) -> Self {
let logger = Logger::new_sub(logger,"camera");
pub fn new(logger: impl AnyLogger) -> Self {
let logger = Logger::new_sub(logger, "camera");
let display_object = display::object::Instance::new(&logger);
let data = Camera2dData::new(logger,&display_object);
let data = Rc::new(RefCell::new(data));
Self {display_object,data}
let data = Camera2dData::new(logger, &display_object);
let data = Rc::new(RefCell::new(data));
Self { display_object, data }
}
}
@ -375,8 +386,8 @@ impl Camera2d {
impl Camera2d {
/// Sets screen dimensions.
pub fn set_screen(&self, width:f32, height:f32) {
self.data.borrow_mut().set_screen(width,height)
pub fn set_screen(&self, width: f32, height: f32) {
self.data.borrow_mut().set_screen(width, height)
}
/// Resets the zoom of the camera to the 1.0 value.
@ -385,17 +396,17 @@ impl Camera2d {
}
/// Update all dirty camera parameters and compute updated view-projection matrix.
pub fn update(&self, scene:&Scene) -> bool {
pub fn update(&self, scene: &Scene) -> bool {
self.data.borrow_mut().update(scene)
}
/// Adds a callback to notify when `zoom` is updated.
pub fn add_zoom_update_callback<F:ZoomUpdateFn>(&self, f:F) -> callback::Handle {
pub fn add_zoom_update_callback<F: ZoomUpdateFn>(&self, f: F) -> callback::Handle {
self.data.borrow_mut().add_zoom_update_callback(f)
}
/// Adds a callback to notify when `screen` is updated.
pub fn add_screen_update_callback<F:ScreenUpdateFn>(&self, f:F) -> callback::Handle {
pub fn add_screen_update_callback<F: ScreenUpdateFn>(&self, f: F) -> callback::Handle {
self.data.borrow_mut().add_screen_update_callback(f)
}
}
@ -422,7 +433,7 @@ impl Camera2d {
}
pub fn fovy(&self) -> f32 {
(1.0 / self.projection_matrix()[(1,1)]).atan() * 2.0
(1.0 / self.projection_matrix()[(1, 1)]).atan() * 2.0
}
pub fn half_fovy_slope(&self) -> f32 {
@ -463,11 +474,11 @@ impl Camera2d {
#[allow(missing_docs)]
impl Camera2d {
pub fn mod_position<F:FnOnce(&mut Vector3<f32>)>(&self, f:F) {
pub fn mod_position<F: FnOnce(&mut Vector3<f32>)>(&self, f: F) {
self.data.borrow_mut().mod_position(f)
}
pub fn set_position(&self, value:Vector3<f32>) {
pub fn set_position(&self, value: Vector3<f32>) {
self.data.borrow_mut().set_position(value)
}
}

View File

@ -10,22 +10,30 @@ use crate::prelude::*;
/// Alignment abstraction. Example usage is camera origin placement allowing it to behave correctly
/// when scaling the scene.
#[derive(Clone,Copy,Debug)]
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub struct Alignment {
pub horizontal : Horizontal,
pub vertical : Vertical,
pub horizontal: Horizontal,
pub vertical: Vertical,
}
/// Horizontal alignments.
#[derive(Clone,Copy,Debug)]
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub enum Horizontal {Left,Center,Right}
pub enum Horizontal {
Left,
Center,
Right,
}
/// Vertical alignments.
#[derive(Clone,Copy,Debug)]
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub enum Vertical {Top,Center,Bottom}
pub enum Vertical {
Top,
Center,
Bottom,
}
// === Smart Constructors ===
@ -34,34 +42,72 @@ pub enum Vertical {Top,Center,Bottom}
#[allow(missing_docs)]
impl Alignment {
/// Constructor.
pub fn new(horizontal:Horizontal, vertical:Vertical) -> Self {
Self {horizontal,vertical}
pub fn new(horizontal: Horizontal, vertical: Vertical) -> Self {
Self { horizontal, vertical }
}
pub fn center () -> Self { Self::new( Horizontal::Center , Vertical::Center ) }
pub fn bottom_left () -> Self { Self::new( Horizontal::Left , Vertical::Bottom ) }
pub fn bottom_right () -> Self { Self::new( Horizontal::Right , Vertical::Bottom ) }
pub fn bottom_center () -> Self { Self::new( Horizontal::Center , Vertical::Bottom ) }
pub fn top_left () -> Self { Self::new( Horizontal::Left , Vertical::Top ) }
pub fn top_right () -> Self { Self::new( Horizontal::Right , Vertical::Top ) }
pub fn top_center () -> Self { Self::new( Horizontal::Center , Vertical::Top ) }
pub fn center_left () -> Self { Self::new( Horizontal::Left , Vertical::Center ) }
pub fn center_right () -> Self { Self::new( Horizontal::Right , Vertical::Center ) }
pub fn center() -> Self {
Self::new(Horizontal::Center, Vertical::Center)
}
pub fn bottom_left() -> Self {
Self::new(Horizontal::Left, Vertical::Bottom)
}
pub fn bottom_right() -> Self {
Self::new(Horizontal::Right, Vertical::Bottom)
}
pub fn bottom_center() -> Self {
Self::new(Horizontal::Center, Vertical::Bottom)
}
pub fn top_left() -> Self {
Self::new(Horizontal::Left, Vertical::Top)
}
pub fn top_right() -> Self {
Self::new(Horizontal::Right, Vertical::Top)
}
pub fn top_center() -> Self {
Self::new(Horizontal::Center, Vertical::Top)
}
pub fn center_left() -> Self {
Self::new(Horizontal::Left, Vertical::Center)
}
pub fn center_right() -> Self {
Self::new(Horizontal::Right, Vertical::Center)
}
}
// === Defaults ===
impl Default for Horizontal { fn default() -> Self { Self::Left } }
impl Default for Vertical { fn default() -> Self { Self::Bottom } }
impl Default for Horizontal {
fn default() -> Self {
Self::Left
}
}
impl Default for Vertical {
fn default() -> Self {
Self::Bottom
}
}
impl Default for Alignment {
fn default() -> Self {
let horizontal = default();
let vertical = default();
Self {horizontal,vertical}
let vertical = default();
Self { horizontal, vertical }
}
}
impl From<&Horizontal> for Horizontal { fn from(t:&Horizontal) -> Self { *t } }
impl From<&Vertical> for Vertical { fn from(t:&Vertical) -> Self { *t } }
impl From<&Alignment> for Alignment { fn from(t:&Alignment) -> Self { *t } }
impl From<&Horizontal> for Horizontal {
fn from(t: &Horizontal) -> Self {
*t
}
}
impl From<&Vertical> for Vertical {
fn from(t: &Vertical) -> Self {
*t
}
}
impl From<&Alignment> for Alignment {
fn from(t: &Alignment) -> Self {
*t
}
}

View File

@ -21,33 +21,39 @@ use events::ZoomEvent;
/// Navigator enables camera navigation with mouse interactions.
#[derive(Debug)]
pub struct NavigatorModel {
_events : NavigatorEvents,
simulator : physics::inertia::DynSimulator<Vector3>,
resize_callback : callback::Handle,
zoom_speed : SharedSwitch<f32>,
pan_speed : SharedSwitch<f32>,
_events: NavigatorEvents,
simulator: physics::inertia::DynSimulator<Vector3>,
resize_callback: callback::Handle,
zoom_speed: SharedSwitch<f32>,
pan_speed: SharedSwitch<f32>,
/// Indicates whether events handled the navigator should be stopped from propagating further
/// after being handled by the Navigator.
disable_events : Rc<Cell<bool>>,
disable_events: Rc<Cell<bool>>,
}
impl NavigatorModel {
pub fn new(scene:&Scene, camera:&Camera2d) -> Self {
let zoom_speed = Rc::new(Cell::new(Switch::On(10.0/1000.0)));
let pan_speed = Rc::new(Cell::new(Switch::On(1.0)));
let min_zoom = 10.0;
let max_zoom = 10000.0;
let disable_events = Rc::new(Cell::new(true));
let (simulator,resize_callback,_events) = Self::start_navigator_events
(scene,camera,min_zoom,max_zoom,Rc::clone(&zoom_speed),Rc::clone(&pan_speed),
Rc::clone(&disable_events));
Self {_events,simulator,resize_callback,zoom_speed,pan_speed,disable_events}
pub fn new(scene: &Scene, camera: &Camera2d) -> Self {
let zoom_speed = Rc::new(Cell::new(Switch::On(10.0 / 1000.0)));
let pan_speed = Rc::new(Cell::new(Switch::On(1.0)));
let min_zoom = 10.0;
let max_zoom = 10000.0;
let disable_events = Rc::new(Cell::new(true));
let (simulator, resize_callback, _events) = Self::start_navigator_events(
scene,
camera,
min_zoom,
max_zoom,
Rc::clone(&zoom_speed),
Rc::clone(&pan_speed),
Rc::clone(&disable_events),
);
Self { _events, simulator, resize_callback, zoom_speed, pan_speed, disable_events }
}
fn create_simulator(camera:&Camera2d) -> physics::inertia::DynSimulator<Vector3> {
fn create_simulator(camera: &Camera2d) -> physics::inertia::DynSimulator<Vector3> {
let camera_ref = camera.clone_ref();
let on_step = Box::new(move |p:Vector3| camera_ref.set_position(p));
let simulator = physics::inertia::DynSimulator::new(on_step,(),());
let on_step = Box::new(move |p: Vector3| camera_ref.set_position(p));
let simulator = physics::inertia::DynSimulator::new(on_step, (), ());
// FIXME[WD]: This one is emitting camera position in next frame, which is not intended.
// Should be fixed when reworking navigator to use FRP events.
simulator.set_value(camera.position());
@ -55,16 +61,16 @@ impl NavigatorModel {
simulator
}
fn start_navigator_events
( scene : &Scene
, camera : &Camera2d
, min_zoom : f32
, max_zoom : f32
, zoom_speed : SharedSwitch<f32>
, pan_speed : SharedSwitch<f32>
, disable_events : Rc<Cell<bool>>
) -> (physics::inertia::DynSimulator<Vector3>,callback::Handle,NavigatorEvents) {
let simulator = Self::create_simulator(camera);
fn start_navigator_events(
scene: &Scene,
camera: &Camera2d,
min_zoom: f32,
max_zoom: f32,
zoom_speed: SharedSwitch<f32>,
pan_speed: SharedSwitch<f32>,
disable_events: Rc<Cell<bool>>,
) -> (physics::inertia::DynSimulator<Vector3>, callback::Handle, NavigatorEvents) {
let simulator = Self::create_simulator(camera);
let panning_callback = enclose!((scene,camera,mut simulator,pan_speed) move |pan: PanEvent| {
let fovy_slope = camera.half_fovy_slope();
let distance = camera.position().z;
@ -81,7 +87,7 @@ impl NavigatorModel {
simulator.set_value(position);
simulator.set_target_value(position);
simulator.set_velocity(default());
})
}),
);
let zoom_callback = enclose!((scene,camera,simulator) move |zoom:ZoomEvent| {
@ -109,9 +115,18 @@ impl NavigatorModel {
position += direction * zoom_factor;
simulator.set_target_value(position);
});
(simulator,resize_callback, NavigatorEvents::new(&scene.mouse.mouse_manager,
panning_callback,zoom_callback,
zoom_speed,pan_speed,disable_events))
(
simulator,
resize_callback,
NavigatorEvents::new(
&scene.mouse.mouse_manager,
panning_callback,
zoom_callback,
zoom_speed,
pan_speed,
disable_events,
),
)
}
pub fn enable(&self) {
@ -134,18 +149,17 @@ impl NavigatorModel {
// =================
/// Navigator enables camera navigation with mouse interactions.
#[derive(Clone,CloneRef,Debug,Shrinkwrap)]
#[derive(Clone, CloneRef, Debug, Shrinkwrap)]
pub struct Navigator {
#[shrinkwrap(main_field)]
model : Rc<NavigatorModel>,
model: Rc<NavigatorModel>,
}
impl Navigator {
pub fn new(scene:&Scene, camera:&Camera2d) -> Self {
let model = Rc::new(NavigatorModel::new(scene,camera));
Navigator{model}
pub fn new(scene: &Scene, camera: &Camera2d) -> Self {
let model = Rc::new(NavigatorModel::new(scene, camera));
Navigator { model }
}
}
@ -157,13 +171,12 @@ impl Navigator {
type SharedSwitch<T> = Rc<Cell<Switch<T>>>;
/// Normalize a `point` in (0..dimension.x, 0..dimension.y) to (0..1, 0..1).
fn normalize_point2
(point:Vector2<f32>, dimension:Vector2<f32>) -> Vector2<f32> {
fn normalize_point2(point: Vector2<f32>, dimension: Vector2<f32>) -> Vector2<f32> {
Vector2::new(point.x / dimension.x, point.y / dimension.y)
}
/// Transforms a `point` normalized in (0..1, 0..1) to (a..b,a..b).
fn normalized_to_range2(point:Vector2<f32>, a:f32, b:f32) -> Vector2<f32> {
fn normalized_to_range2(point: Vector2<f32>, a: f32, b: f32) -> Vector2<f32> {
let width = b - a;
Vector2::new(point.x * width + a, point.y * width + a)
}

View File

@ -1,12 +1,12 @@
use crate::prelude::*;
use crate::control::callback;
use crate::control::io::mouse;
use crate::control::io::mouse::MouseManager;
use crate::control::callback;
use crate::display::navigation::navigator::SharedSwitch;
use nalgebra::Vector2;
use nalgebra::zero;
use nalgebra::Vector2;
@ -18,12 +18,12 @@ pub trait FnZoomEvent = FnMut(ZoomEvent) + 'static;
/// A struct holding zoom event information, such as the focus point and the amount of zoom.
pub struct ZoomEvent {
pub focus : Vector2<f32>,
pub amount : f32
pub focus: Vector2<f32>,
pub amount: f32,
}
impl ZoomEvent {
fn new(focus:Vector2<f32>, amount:f32, zoom_speed:f32) -> Self {
fn new(focus: Vector2<f32>, amount: f32, zoom_speed: f32) -> Self {
let amount = amount * zoom_speed;
Self { focus, amount }
}
@ -39,11 +39,11 @@ pub trait FnPanEvent = FnMut(PanEvent) + 'static;
/// A struct holding pan event information.
pub struct PanEvent {
pub movement : Vector2<f32>
pub movement: Vector2<f32>,
}
impl PanEvent {
fn new(movement:Vector2<f32>) -> Self {
fn new(movement: Vector2<f32>) -> Self {
Self { movement }
}
}
@ -54,10 +54,10 @@ impl PanEvent {
// === MovementType ===
// ====================
#[derive(PartialEq,Clone,Copy,Debug)]
#[derive(PartialEq, Clone, Copy, Debug)]
enum MovementType {
Pan,
Zoom { focus : Vector2<f32> }
Zoom { focus: Vector2<f32> },
}
@ -69,16 +69,16 @@ enum MovementType {
#[derive(Derivative)]
#[derivative(Debug)]
struct NavigatorEventsProperties {
zoom_speed : SharedSwitch<f32>,
pan_speed : SharedSwitch<f32>,
disable_events : Rc<Cell<bool>>,
movement_type : Option<MovementType>,
last_mouse_position : Vector2<f32>,
mouse_position : Vector2<f32>,
#[derivative(Debug="ignore")]
pan_callback : Box<dyn FnPanEvent>,
#[derivative(Debug="ignore")]
zoom_callback : Box<dyn FnZoomEvent>,
zoom_speed: SharedSwitch<f32>,
pan_speed: SharedSwitch<f32>,
disable_events: Rc<Cell<bool>>,
movement_type: Option<MovementType>,
last_mouse_position: Vector2<f32>,
mouse_position: Vector2<f32>,
#[derivative(Debug = "ignore")]
pan_callback: Box<dyn FnPanEvent>,
#[derivative(Debug = "ignore")]
zoom_callback: Box<dyn FnZoomEvent>,
}
@ -89,20 +89,21 @@ struct NavigatorEventsProperties {
#[derive(Debug)]
struct NavigatorEventsData {
properties : RefCell<NavigatorEventsProperties>
properties: RefCell<NavigatorEventsProperties>,
}
impl NavigatorEventsData {
fn new
( pan_callback:Box<dyn FnPanEvent>
, zoom_callback:Box<dyn FnZoomEvent>
, zoom_speed:SharedSwitch<f32>
, pan_speed:SharedSwitch<f32>
, disable_events:Rc<Cell<bool>>) -> Rc<Self> {
let mouse_position = zero();
fn new(
pan_callback: Box<dyn FnPanEvent>,
zoom_callback: Box<dyn FnZoomEvent>,
zoom_speed: SharedSwitch<f32>,
pan_speed: SharedSwitch<f32>,
disable_events: Rc<Cell<bool>>,
) -> Rc<Self> {
let mouse_position = zero();
let last_mouse_position = zero();
let movement_type = None;
let properties = RefCell::new(NavigatorEventsProperties {
let movement_type = None;
let properties = RefCell::new(NavigatorEventsProperties {
zoom_speed,
pan_speed,
disable_events,
@ -111,12 +112,11 @@ impl NavigatorEventsData {
mouse_position,
pan_callback,
zoom_callback,
});
Rc::new(Self {properties})
Rc::new(Self { properties })
}
fn on_zoom(&self, event:ZoomEvent) {
fn on_zoom(&self, event: ZoomEvent) {
(&mut self.properties.borrow_mut().zoom_callback)(event);
}
@ -158,14 +158,14 @@ impl NavigatorEventsData {
// === Setters ===
impl NavigatorEventsData {
fn set_movement_type(&self, movement_type:Option<MovementType>) {
fn set_movement_type(&self, movement_type: Option<MovementType>) {
self.properties.borrow_mut().movement_type = movement_type;
}
fn set_mouse_position(&self, mouse_position:Vector2<f32>) {
let mut properties = self.properties.borrow_mut();
fn set_mouse_position(&self, mouse_position: Vector2<f32>) {
let mut properties = self.properties.borrow_mut();
properties.last_mouse_position = properties.mouse_position;
properties.mouse_position = mouse_position;
properties.mouse_position = mouse_position;
}
}
@ -178,44 +178,50 @@ impl NavigatorEventsData {
#[derive(Derivative)]
#[derivative(Debug)]
pub struct NavigatorEvents {
data : Rc<NavigatorEventsData>,
mouse_manager : MouseManager,
#[derivative(Debug="ignore")]
mouse_down : Option<callback::Handle>,
#[derivative(Debug="ignore")]
mouse_up : Option<callback::Handle>,
#[derivative(Debug="ignore")]
mouse_move : Option<callback::Handle>,
#[derivative(Debug="ignore")]
mouse_leave : Option<callback::Handle>,
#[derivative(Debug="ignore")]
wheel_zoom : Option<callback::Handle>
data: Rc<NavigatorEventsData>,
mouse_manager: MouseManager,
#[derivative(Debug = "ignore")]
mouse_down: Option<callback::Handle>,
#[derivative(Debug = "ignore")]
mouse_up: Option<callback::Handle>,
#[derivative(Debug = "ignore")]
mouse_move: Option<callback::Handle>,
#[derivative(Debug = "ignore")]
mouse_leave: Option<callback::Handle>,
#[derivative(Debug = "ignore")]
wheel_zoom: Option<callback::Handle>,
}
impl NavigatorEvents {
pub fn new
<P,Z>(mouse_manager:&MouseManager, pan_callback:P, zoom_callback:Z,
zoom_speed:SharedSwitch<f32>,pan_speed:SharedSwitch<f32>,disable_events:Rc<Cell<bool>>) -> Self
where P : FnPanEvent, Z : FnZoomEvent {
pub fn new<P, Z>(
mouse_manager: &MouseManager,
pan_callback: P,
zoom_callback: Z,
zoom_speed: SharedSwitch<f32>,
pan_speed: SharedSwitch<f32>,
disable_events: Rc<Cell<bool>>,
) -> Self
where
P: FnPanEvent,
Z: FnZoomEvent,
{
let mouse_manager = mouse_manager.clone_ref();
let pan_callback = Box::new(pan_callback);
let pan_callback = Box::new(pan_callback);
let zoom_callback = Box::new(zoom_callback);
let mouse_move = default();
let mouse_up = default();
let mouse_down = default();
let wheel_zoom = default();
let mouse_leave = default();
let data = NavigatorEventsData::new
(pan_callback,zoom_callback,zoom_speed,pan_speed,disable_events);
let mut event_handler = Self {
data,
mouse_manager,
mouse_down,
mouse_up,
mouse_move,
mouse_leave,
wheel_zoom
};
let mouse_move = default();
let mouse_up = default();
let mouse_down = default();
let wheel_zoom = default();
let mouse_leave = default();
let data = NavigatorEventsData::new(
pan_callback,
zoom_callback,
zoom_speed,
pan_speed,
disable_events,
);
let mut event_handler =
Self { data, mouse_manager, mouse_down, mouse_up, mouse_move, mouse_leave, wheel_zoom };
event_handler.initialize_mouse_events();
event_handler
@ -229,8 +235,8 @@ impl NavigatorEvents {
}
fn initialize_wheel_zoom(&mut self) {
let data = Rc::downgrade(&self.data);
let listener = self.mouse_manager.on_wheel.add(move |event:&mouse::OnWheel| {
let data = Rc::downgrade(&self.data);
let listener = self.mouse_manager.on_wheel.add(move |event: &mouse::OnWheel| {
if let Some(data) = data.upgrade() {
if data.events_disabled() {
event.prevent_default();
@ -240,17 +246,17 @@ impl NavigatorEvents {
// being applied to the whole IDE, thus we need to do this always when ctrl is
// pressed.
event.prevent_default();
let position = data.mouse_position();
let position = data.mouse_position();
let zoom_speed = data.zoom_speed();
let movement = Vector2::new(event.delta_x() as f32, -event.delta_y() as f32);
let amount = movement_to_zoom(movement);
let zoom_event = ZoomEvent::new(position,amount,zoom_speed);
let movement = Vector2::new(event.delta_x() as f32, -event.delta_y() as f32);
let amount = movement_to_zoom(movement);
let zoom_event = ZoomEvent::new(position, amount, zoom_speed);
data.on_zoom(zoom_event);
} else {
let x = -event.delta_x() as f32;
let y = event.delta_y() as f32;
let x = -event.delta_x() as f32;
let y = event.delta_y() as f32;
let pan_speed = data.pan_speed();
let movement = Vector2::new(x,y) * pan_speed;
let movement = Vector2::new(x, y) * pan_speed;
let pan_event = PanEvent::new(movement);
data.on_pan(pan_event);
}
@ -260,21 +266,19 @@ impl NavigatorEvents {
}
fn initialize_mouse_start_event(&mut self) {
let data = Rc::downgrade(&self.data);
let listener = self.mouse_manager.on_down.add(move |event:&mouse::OnDown| {
let data = Rc::downgrade(&self.data);
let listener = self.mouse_manager.on_down.add(move |event: &mouse::OnDown| {
if let Some(data) = data.upgrade() {
if data.events_disabled() {
event.prevent_default();
}
match event.button() {
mouse::MiddleButton => {
data.set_movement_type(Some(MovementType::Pan))
},
mouse::MiddleButton => data.set_movement_type(Some(MovementType::Pan)),
mouse::SecondaryButton => {
let focus = event.position_relative_to_event_handler();
data.set_movement_type(Some(MovementType::Zoom{focus}))
},
_ => ()
data.set_movement_type(Some(MovementType::Zoom { focus }))
}
_ => (),
}
}
});
@ -282,8 +286,8 @@ impl NavigatorEvents {
}
fn initialize_mouse_end_event(&mut self) {
let data = Rc::downgrade(&self.data);
let listener = self.mouse_manager.on_up.add(move |event:&mouse::OnUp| {
let data = Rc::downgrade(&self.data);
let listener = self.mouse_manager.on_up.add(move |event: &mouse::OnUp| {
if let Some(data) = data.upgrade() {
if data.events_disabled() {
event.prevent_default();
@ -293,8 +297,8 @@ impl NavigatorEvents {
});
self.mouse_up = Some(listener);
let data = Rc::downgrade(&self.data);
let listener = self.mouse_manager.on_leave.add(move |event:&mouse::OnLeave| {
let data = Rc::downgrade(&self.data);
let listener = self.mouse_manager.on_leave.add(move |event: &mouse::OnLeave| {
if let Some(data) = data.upgrade() {
if data.events_disabled() {
event.prevent_default();
@ -306,8 +310,8 @@ impl NavigatorEvents {
}
fn initialize_mouse_move_event(&mut self) {
let data = Rc::downgrade(&self.data);
let listener = self.mouse_manager.on_move.add(move |event:&mouse::OnMove| {
let data = Rc::downgrade(&self.data);
let listener = self.mouse_manager.on_move.add(move |event: &mouse::OnMove| {
if let Some(data) = data.upgrade() {
if data.events_disabled() {
event.prevent_default();
@ -320,11 +324,11 @@ impl NavigatorEvents {
if let Some(movement_type) = data.movement_type() {
match movement_type {
MovementType::Zoom { focus } => {
let zoom_speed = data.zoom_speed();
let zoom_speed = data.zoom_speed();
let zoom_amount = movement_to_zoom(movement);
let zoom_event = ZoomEvent::new(focus,zoom_amount,zoom_speed);
let zoom_event = ZoomEvent::new(focus, zoom_amount, zoom_speed);
data.on_zoom(zoom_event);
},
}
MovementType::Pan => {
let pan_event = PanEvent::new(movement);
data.on_pan(pan_event);
@ -337,8 +341,8 @@ impl NavigatorEvents {
}
}
fn movement_to_zoom(v:Vector2<f32>) -> f32 {
let len = v.magnitude();
fn movement_to_zoom(v: Vector2<f32>) -> f32 {
let len = v.magnitude();
// The zoom amount is a movement of camera along Z-axis, so positive values zoom out, and
// negative zoom in.
let sign = if v.x + v.y < 0.0 { 1.0 } else { -1.0 };

View File

@ -16,6 +16,6 @@ pub use class::Any;
/// Common traits.
pub mod traits {
// Read the Rust Style Guide to learn more about the used naming.
pub use super::Object as TRAIT_Object;
pub use super::Object as TRAIT_Object;
pub use super::ObjectOps as TRAIT_ObjectOps;
}

File diff suppressed because it is too large Load Diff

View File

@ -10,11 +10,20 @@ use crate::prelude::*;
/// Defines the order in which particular axis coordinates are processed. Used for example to define
/// the rotation order in `DisplayObject`.
#[derive(Clone,Copy,Debug)]
pub enum AxisOrder {XYZ,XZY,YXZ,YZX,ZXY,ZYX}
#[derive(Clone, Copy, Debug)]
pub enum AxisOrder {
XYZ,
XZY,
YXZ,
YZX,
ZXY,
ZYX,
}
impl Default for AxisOrder {
fn default() -> Self { Self::XYZ }
fn default() -> Self {
Self::XYZ
}
}
@ -25,18 +34,20 @@ impl Default for AxisOrder {
/// Defines the order in which transformations (scale, rotate, translate) are applied to a
/// particular object.
#[derive(Clone,Copy,Debug)]
#[derive(Clone, Copy, Debug)]
pub enum TransformOrder {
ScaleRotateTranslate,
ScaleTranslateRotate,
RotateScaleTranslate,
RotateTranslateScale,
TranslateRotateScale,
TranslateScaleRotate
TranslateScaleRotate,
}
impl Default for TransformOrder {
fn default() -> Self { Self::ScaleRotateTranslate }
fn default() -> Self {
Self::ScaleRotateTranslate
}
}
@ -49,29 +60,31 @@ impl Default for TransformOrder {
/// You can use methods like `matrix` to get a combined transformation matrix. Bear in mind that
/// the matrix will always be recomputed from scratch. This structure does not contain any caching
/// mechanisms.
#[derive(Clone,Copy,Debug)]
#[derive(Clone, Copy, Debug)]
pub struct Transform {
pub position : Vector3<f32>,
pub scale : Vector3<f32>,
pub rotation : Vector3<f32>,
pub transform_order : TransformOrder,
pub rotation_order : AxisOrder,
pub position: Vector3<f32>,
pub scale: Vector3<f32>,
pub rotation: Vector3<f32>,
pub transform_order: TransformOrder,
pub rotation_order: AxisOrder,
}
impl Default for Transform {
fn default() -> Self {
let position = Vector3::new(0.0,0.0,0.0);
let scale = Vector3::new(1.0,1.0,1.0);
let rotation = Vector3::new(0.0,0.0,0.0);
let position = Vector3::new(0.0, 0.0, 0.0);
let scale = Vector3::new(1.0, 1.0, 1.0);
let rotation = Vector3::new(0.0, 0.0, 0.0);
let transform_order = default();
let rotation_order = default();
Self {position,scale,rotation,transform_order,rotation_order}
let rotation_order = default();
Self { position, scale, rotation, transform_order, rotation_order }
}
}
impl Transform {
/// Creates a new transformation object.
pub fn new() -> Self { default() }
pub fn new() -> Self {
default()
}
/// Computes transformation matrix from the provided scale, rotation, and
/// translation components, based on the transformation and rotation orders.
@ -80,34 +93,34 @@ impl Transform {
let matrix_ref = &mut matrix;
match self.transform_order {
TransformOrder::ScaleRotateTranslate => {
self.append_scale (matrix_ref);
self.append_rotation (matrix_ref);
self.append_translation (matrix_ref);
self.append_scale(matrix_ref);
self.append_rotation(matrix_ref);
self.append_translation(matrix_ref);
}
TransformOrder::ScaleTranslateRotate => {
self.append_scale (matrix_ref);
self.append_translation (matrix_ref);
self.append_rotation (matrix_ref);
self.append_scale(matrix_ref);
self.append_translation(matrix_ref);
self.append_rotation(matrix_ref);
}
TransformOrder::RotateScaleTranslate => {
self.append_rotation (matrix_ref);
self.append_scale (matrix_ref);
self.append_translation (matrix_ref);
self.append_rotation(matrix_ref);
self.append_scale(matrix_ref);
self.append_translation(matrix_ref);
}
TransformOrder::RotateTranslateScale => {
self.append_rotation (matrix_ref);
self.append_translation (matrix_ref);
self.append_scale (matrix_ref);
self.append_rotation(matrix_ref);
self.append_translation(matrix_ref);
self.append_scale(matrix_ref);
}
TransformOrder::TranslateRotateScale => {
self.append_translation (matrix_ref);
self.append_rotation (matrix_ref);
self.append_scale (matrix_ref);
self.append_translation(matrix_ref);
self.append_rotation(matrix_ref);
self.append_scale(matrix_ref);
}
TransformOrder::TranslateScaleRotate => {
self.append_translation (matrix_ref);
self.append_scale (matrix_ref);
self.append_rotation (matrix_ref);
self.append_translation(matrix_ref);
self.append_scale(matrix_ref);
self.append_rotation(matrix_ref);
}
}
matrix
@ -129,15 +142,15 @@ impl Transform {
}
}
fn append_translation(&self, m:&mut Matrix4<f32>) {
fn append_translation(&self, m: &mut Matrix4<f32>) {
m.append_translation_mut(&self.position);
}
fn append_rotation(&self, m:&mut Matrix4<f32>) {
fn append_rotation(&self, m: &mut Matrix4<f32>) {
*m = self.rotation_matrix() * (*m);
}
fn append_scale(&self, m:&mut Matrix4<f32>) {
fn append_scale(&self, m: &mut Matrix4<f32>) {
m.append_nonuniform_scaling_mut(&self.scale);
}
}
@ -151,24 +164,24 @@ impl Transform {
/// The same as `Transform` but with caching. It contains cached transformation matrix and dirty
/// flags which are set after fields are modified. You can use the `update` function to recompute
/// the matrix.
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
#[allow(missing_copy_implementations)]
pub struct CachedTransform {
transform : Transform,
transform_matrix : Matrix4<f32>,
origin : Matrix4<f32>,
pub matrix : Matrix4<f32>,
pub dirty : bool,
transform: Transform,
transform_matrix: Matrix4<f32>,
origin: Matrix4<f32>,
pub matrix: Matrix4<f32>,
pub dirty: bool,
}
impl Default for CachedTransform {
fn default() -> Self {
let transform = default();
let transform = default();
let transform_matrix = Matrix4::identity();
let origin = Matrix4::identity();
let matrix = Matrix4::identity();
let dirty = default();
Self {transform,transform_matrix,origin,matrix,dirty}
let origin = Matrix4::identity();
let matrix = Matrix4::identity();
let dirty = default();
Self { transform, transform_matrix, origin, matrix, dirty }
}
}
@ -179,9 +192,9 @@ impl CachedTransform {
}
/// Update the transformation matrix and return information if the data was really updated.
pub fn update(&mut self, new_origin:Option<Matrix4<f32>>) -> bool {
pub fn update(&mut self, new_origin: Option<Matrix4<f32>>) -> bool {
let origin_changed = new_origin.is_some();
let changed = self.dirty || origin_changed;
let changed = self.dirty || origin_changed;
if changed {
if self.dirty {
self.transform_matrix = self.transform.matrix();
@ -215,14 +228,14 @@ impl CachedTransform {
}
pub fn global_position(&self) -> Vector3<f32> {
(self.matrix * Vector4::new(0.0,0.0,0.0,1.0)).xyz()
(self.matrix * Vector4::new(0.0, 0.0, 0.0, 1.0)).xyz()
}
}
// === Setters ===
impl CachedTransform{
impl CachedTransform {
pub fn position_mut(&mut self) -> &mut Vector3<f32> {
self.dirty = true;
&mut self.transform.position
@ -238,27 +251,27 @@ impl CachedTransform{
&mut self.transform.scale
}
pub fn set_position(&mut self, t:Vector3<f32>) {
pub fn set_position(&mut self, t: Vector3<f32>) {
*self.position_mut() = t;
}
pub fn set_rotation(&mut self, t:Vector3<f32>) {
pub fn set_rotation(&mut self, t: Vector3<f32>) {
*self.rotation_mut() = t;
}
pub fn set_scale(&mut self, t:Vector3<f32>) {
pub fn set_scale(&mut self, t: Vector3<f32>) {
*self.scale_mut() = t;
}
pub fn mod_position<F:FnOnce(&mut Vector3<f32>)>(&mut self, f:F) {
pub fn mod_position<F: FnOnce(&mut Vector3<f32>)>(&mut self, f: F) {
f(self.position_mut());
}
pub fn mod_rotation<F:FnOnce(&mut Vector3<f32>)>(&mut self, f:F) {
pub fn mod_rotation<F: FnOnce(&mut Vector3<f32>)>(&mut self, f: F) {
f(self.rotation_mut());
}
pub fn mod_scale<F:FnOnce(&mut Vector3<f32>)>(&mut self, f:F) {
pub fn mod_scale<F: FnOnce(&mut Vector3<f32>)>(&mut self, f: F) {
f(self.scale_mut());
}
}

View File

@ -84,9 +84,9 @@ impl {
#[derive(Derivative)]
#[derivative(Debug)]
struct ComposerPass {
#[derivative(Debug="ignore")]
pass : Box<dyn pass::Definition>,
instance : pass::Instance,
#[derivative(Debug = "ignore")]
pass: Box<dyn pass::Definition>,
instance: pass::Instance,
}
impl Deref for ComposerPass {
@ -105,16 +105,16 @@ impl DerefMut for ComposerPass {
impl ComposerPass {
/// Constructor
#[allow(clippy::borrowed_box)]
pub fn new
( context : &Context
, variables : &UniformScope
, mut pass : Box<dyn pass::Definition>
, width : i32
, height : i32
pub fn new(
context: &Context,
variables: &UniformScope,
mut pass: Box<dyn pass::Definition>,
width: i32,
height: i32,
) -> Self {
let instance = pass::Instance::new(context,variables,width,height);
let instance = pass::Instance::new(context, variables, width, height);
pass.initialize(&instance);
Self {pass,instance}
Self { pass, instance }
}
/// Run the pass.

View File

@ -2,8 +2,8 @@
use crate::prelude::*;
use crate::system::gpu::*;
use crate::system::gpu::data::texture::class::TextureOps;
use crate::system::gpu::*;
@ -16,9 +16,9 @@ use crate::system::gpu::data::texture::class::TextureOps;
/// re-initialized (e.g. after changing scene size). Then, the function [`run`] will be called for
/// every registered pass.
#[allow(missing_docs)]
pub trait Definition : CloneBoxedForDefinition + Debug + 'static {
fn initialize (&mut self, _instance:&Instance) {}
fn run (&mut self, _instance:&Instance);
pub trait Definition: CloneBoxedForDefinition + Debug + 'static {
fn initialize(&mut self, _instance: &Instance) {}
fn run(&mut self, _instance: &Instance);
}
clone_boxed!(Definition);
@ -39,58 +39,61 @@ clone_boxed!(Definition);
#[allow(missing_docs)]
#[derive(Debug)]
pub struct Instance {
pub variables : UniformScope,
pub context : Context,
pub width : i32,
pub height : i32,
pub variables: UniformScope,
pub context: Context,
pub width: i32,
pub height: i32,
}
impl Instance {
/// Constructor
#[allow(clippy::borrowed_box)]
pub fn new
( context : &Context
, variables : &UniformScope
, width : i32
, height : i32
) -> Self {
pub fn new(context: &Context, variables: &UniformScope, width: i32, height: i32) -> Self {
let variables = variables.clone_ref();
let context = context.clone();
Self {variables,context,width,height}
let context = context.clone();
Self { variables, context, width, height }
}
/// Create a new texture covering the whole screen and register it in the global uniform scope
/// with the name provided as the configuration argument.
pub fn new_screen_texture(&self, output:&OutputDefinition) -> AnyTextureUniform {
let context = &self.context;
pub fn new_screen_texture(&self, output: &OutputDefinition) -> AnyTextureUniform {
let context = &self.context;
let variables = &self.variables;
let name = format!("pass_{}",output.name);
let args = (self.width,self.height);
let format = output.internal_format;
let name = format!("pass_{}", output.name);
let args = (self.width, self.height);
let format = output.internal_format;
let item_type = output.item_type;
let params = Some(output.texture_parameters);
uniform::get_or_add_gpu_texture_dyn(context,variables,&name,format,item_type,args,params)
let params = Some(output.texture_parameters);
uniform::get_or_add_gpu_texture_dyn(
context, variables, &name, format, item_type, args, params,
)
}
/// Create a new framebuffer from the provided textures.
pub fn new_framebuffer(&self, textures:&[&AnyTextureUniform]) -> Framebuffer {
let context = self.context.clone();
let native = self.context.create_framebuffer().unwrap();
let target = Context::FRAMEBUFFER;
pub fn new_framebuffer(&self, textures: &[&AnyTextureUniform]) -> Framebuffer {
let context = self.context.clone();
let native = self.context.create_framebuffer().unwrap();
let target = Context::FRAMEBUFFER;
let draw_buffers = js_sys::Array::new();
context.bind_framebuffer(target,Some(&native));
for (index,texture) in textures.iter().enumerate() {
let texture_target = Context::TEXTURE_2D;
context.bind_framebuffer(target, Some(&native));
for (index, texture) in textures.iter().enumerate() {
let texture_target = Context::TEXTURE_2D;
let attachment_point = Context::COLOR_ATTACHMENT0 + index as u32;
let gl_texture = texture.gl_texture();
let gl_texture = Some(&gl_texture);
let level = 0;
let gl_texture = texture.gl_texture();
let gl_texture = Some(&gl_texture);
let level = 0;
draw_buffers.push(&attachment_point.into());
context.framebuffer_texture_2d(target,attachment_point,texture_target,gl_texture,level);
context.framebuffer_texture_2d(
target,
attachment_point,
texture_target,
gl_texture,
level,
);
}
context.draw_buffers(&draw_buffers);
context.bind_framebuffer(target,None);
Framebuffer {context,native}
context.bind_framebuffer(target, None);
Framebuffer { context, native }
}
}
@ -105,29 +108,33 @@ impl Instance {
#[allow(missing_docs)]
#[derive(Debug)]
pub struct OutputDefinition {
pub name : String,
pub internal_format : texture::AnyInternalFormat,
pub item_type : texture::AnyItemType,
pub texture_parameters : texture::Parameters,
pub name: String,
pub internal_format: texture::AnyInternalFormat,
pub item_type: texture::AnyItemType,
pub texture_parameters: texture::Parameters,
}
impl OutputDefinition {
/// Constructor.
pub fn new<Name:Str,F:Into<texture::AnyInternalFormat>,T:Into<texture::AnyItemType>>
(name:Name, internal_format:F, item_type:T, texture_parameters:texture::Parameters) -> Self {
let name = name.into();
pub fn new<Name: Str, F: Into<texture::AnyInternalFormat>, T: Into<texture::AnyItemType>>(
name: Name,
internal_format: F,
item_type: T,
texture_parameters: texture::Parameters,
) -> Self {
let name = name.into();
let internal_format = internal_format.into();
let item_type = item_type.into();
Self {name,internal_format,item_type,texture_parameters}
let item_type = item_type.into();
Self { name, internal_format, item_type, texture_parameters }
}
/// Constructor of the RGBA u8 output with default texture parameters. It is the most popular
/// option and you should use it to render colors with your passes.
pub fn new_rgba<Name:Str>(name:Name) -> Self {
let internal_format = texture::Rgba;
let item_type = texture::item_type::u8;
pub fn new_rgba<Name: Str>(name: Name) -> Self {
let internal_format = texture::Rgba;
let item_type = texture::item_type::u8;
let texture_parameters = default();
OutputDefinition::new(name,internal_format,item_type,texture_parameters)
OutputDefinition::new(name, internal_format, item_type, texture_parameters)
}
}
@ -138,10 +145,10 @@ impl OutputDefinition {
// ===================
/// A native WebGL framebuffer object bound to the gl context.
#[derive(Debug,Clone)]
#[derive(Debug, Clone)]
pub struct Framebuffer {
context : Context,
native : web_sys::WebGlFramebuffer,
context: Context,
native: web_sys::WebGlFramebuffer,
}
impl Deref for Framebuffer {
@ -154,6 +161,6 @@ impl Deref for Framebuffer {
impl Framebuffer {
/// Bind the framebuffer to the current WebGL context.
pub fn bind(&self) {
self.context.bind_framebuffer(Context::FRAMEBUFFER,Some(&self.native));
self.context.bind_framebuffer(Context::FRAMEBUFFER, Some(&self.native));
}
}

View File

@ -1,16 +1,16 @@
//! Root module for render passes definitions.
pub mod symbols;
pub mod pixel_read;
pub mod screen;
pub mod symbols;
/// Common types.
pub mod types {
use super::*;
pub use symbols::*;
pub use pixel_read::*;
pub use screen::*;
pub use symbols::*;
}
pub use types::*;

View File

@ -3,8 +3,8 @@
use crate::prelude::*;
use crate::display::render::pass;
use crate::system::gpu::*;
use crate::system::gpu::data::texture::class::TextureOps;
use crate::system::gpu::*;
use crate::system::js::*;
use web_sys::WebGlBuffer;
@ -18,25 +18,25 @@ use web_sys::WebGlSync;
// =========================
/// Internal state for the `PixelReadPass`.
#[derive(Clone,Debug)]
pub struct PixelReadPassData<T:JsTypedArrayItem> {
buffer : WebGlBuffer,
framebuffer : WebGlFramebuffer,
format : texture::AnyFormat,
item_type : texture::AnyItemType,
js_array : JsTypedArray<T>,
#[derive(Clone, Debug)]
pub struct PixelReadPassData<T: JsTypedArrayItem> {
buffer: WebGlBuffer,
framebuffer: WebGlFramebuffer,
format: texture::AnyFormat,
item_type: texture::AnyItemType,
js_array: JsTypedArray<T>,
}
impl<T:JsTypedArrayItem> PixelReadPassData<T> {
impl<T: JsTypedArrayItem> PixelReadPassData<T> {
/// Constructor.
pub fn new
( buffer : WebGlBuffer
, framebuffer : WebGlFramebuffer
, format : texture::AnyFormat
, item_type : texture::AnyItemType
, js_array : JsTypedArray<T>
pub fn new(
buffer: WebGlBuffer,
framebuffer: WebGlFramebuffer,
format: texture::AnyFormat,
item_type: texture::AnyItemType,
js_array: JsTypedArray<T>,
) -> Self {
Self {buffer,framebuffer,format,item_type,js_array}
Self { buffer, framebuffer, format, item_type, js_array }
}
}
@ -47,103 +47,111 @@ impl<T:JsTypedArrayItem> PixelReadPassData<T> {
// =====================
/// Reads the pixel color and stores it in the 'pass_pixel_color' variable.
#[derive(Derivative,Clone)]
#[derive(Derivative, Clone)]
#[derivative(Debug)]
pub struct PixelReadPass<T:JsTypedArrayItem> {
data : Option<PixelReadPassData<T>>,
sync : Option<WebGlSync>,
position : Uniform<Vector2<i32>>,
threshold : usize,
to_next_read : usize,
#[derivative(Debug="ignore")]
callback : Option<Rc<dyn Fn(Vec<T>)>>,
pub struct PixelReadPass<T: JsTypedArrayItem> {
data: Option<PixelReadPassData<T>>,
sync: Option<WebGlSync>,
position: Uniform<Vector2<i32>>,
threshold: usize,
to_next_read: usize,
#[derivative(Debug = "ignore")]
callback: Option<Rc<dyn Fn(Vec<T>)>>,
}
impl<T:JsTypedArrayItem> PixelReadPass<T> {
impl<T: JsTypedArrayItem> PixelReadPass<T> {
/// Constructor.
pub fn new(position:&Uniform<Vector2<i32>>) -> Self {
let data = default();
let sync = default();
let position = position.clone_ref();
let callback = default();
let threshold = 0;
pub fn new(position: &Uniform<Vector2<i32>>) -> Self {
let data = default();
let sync = default();
let position = position.clone_ref();
let callback = default();
let threshold = 0;
let to_next_read = 0;
Self {data,sync,position,threshold,to_next_read,callback}
Self { data, sync, position, threshold, to_next_read, callback }
}
/// Sets a callback which will be evaluated after a successful pixel read action. Please note
/// that it will not be evaluated after each run of this pass, as the read is performed in an
/// asynchronous fashion and can take longer than a single frame.
pub fn set_callback<F:Fn(Vec<T>)+'static>(&mut self, f:F) {
pub fn set_callback<F: Fn(Vec<T>) + 'static>(&mut self, f: F) {
self.callback = Some(Rc::new(f));
}
/// Sets a threshold of how often the pass should be run. Threshold of 0 means that it will be
/// run every time. Threshold of N means that it will be only run every N-th call to the `run`
/// function.
pub fn set_threshold(&mut self, threshold:usize) {
pub fn set_threshold(&mut self, threshold: usize) {
self.threshold = threshold;
}
fn init_if_fresh(&mut self, context:&Context, variables:&UniformScope) {
fn init_if_fresh(&mut self, context: &Context, variables: &UniformScope) {
if self.data.is_none() {
let buffer = context.create_buffer().unwrap();
let buffer = context.create_buffer().unwrap();
let js_array = JsTypedArray::<T>::new_with_length(4);
let target = Context::PIXEL_PACK_BUFFER;
let usage = Context::DYNAMIC_READ;
context.bind_buffer(target,Some(&buffer));
context.buffer_data_with_opt_array_buffer(target,Some(&js_array.buffer()),usage);
let target = Context::PIXEL_PACK_BUFFER;
let usage = Context::DYNAMIC_READ;
context.bind_buffer(target, Some(&buffer));
context.buffer_data_with_opt_array_buffer(target, Some(&js_array.buffer()), usage);
let texture = match variables.get("pass_id").unwrap() {
uniform::AnyUniform::Texture(t) => t,
_ => panic!("Pass internal error. Unmatched types.")
_ => panic!("Pass internal error. Unmatched types."),
};
let format = texture.get_format();
let item_type = texture.get_item_type();
let gl_texture = texture.gl_texture();
let framebuffer = context.create_framebuffer().unwrap();
let target = Context::FRAMEBUFFER;
let texture_target = Context::TEXTURE_2D;
let format = texture.get_format();
let item_type = texture.get_item_type();
let gl_texture = texture.gl_texture();
let framebuffer = context.create_framebuffer().unwrap();
let target = Context::FRAMEBUFFER;
let texture_target = Context::TEXTURE_2D;
let attachment_point = Context::COLOR_ATTACHMENT0;
let gl_texture = Some(&gl_texture);
let level = 0;
context.bind_framebuffer(target,Some(&framebuffer));
context.framebuffer_texture_2d(target,attachment_point,texture_target,gl_texture,level);
let gl_texture = Some(&gl_texture);
let level = 0;
context.bind_framebuffer(target, Some(&framebuffer));
context.framebuffer_texture_2d(
target,
attachment_point,
texture_target,
gl_texture,
level,
);
let data = PixelReadPassData::new(buffer,framebuffer,format,item_type,js_array);
let data = PixelReadPassData::new(buffer, framebuffer, format, item_type, js_array);
self.data = Some(data);
}
}
fn run_not_synced(&mut self, context:&Context) {
let data = self.data.as_ref().unwrap();
fn run_not_synced(&mut self, context: &Context) {
let data = self.data.as_ref().unwrap();
let position = self.position.get();
let width = 1;
let height = 1;
let format = data.format.to::<GlEnum>().into();
let typ = data.item_type.to::<GlEnum>().into();
let offset = 0;
context.bind_framebuffer(Context::FRAMEBUFFER,Some(&data.framebuffer));
context.bind_buffer(Context::PIXEL_PACK_BUFFER,Some(&data.buffer));
context.read_pixels_with_i32(position.x,position.y,width,height,format,typ,offset).unwrap();
let width = 1;
let height = 1;
let format = data.format.to::<GlEnum>().into();
let typ = data.item_type.to::<GlEnum>().into();
let offset = 0;
context.bind_framebuffer(Context::FRAMEBUFFER, Some(&data.framebuffer));
context.bind_buffer(Context::PIXEL_PACK_BUFFER, Some(&data.buffer));
context
.read_pixels_with_i32(position.x, position.y, width, height, format, typ, offset)
.unwrap();
let condition = Context::SYNC_GPU_COMMANDS_COMPLETE;
let flags = 0;
let sync = context.fence_sync(condition,flags).unwrap();
self.sync = Some(sync);
let flags = 0;
let sync = context.fence_sync(condition, flags).unwrap();
self.sync = Some(sync);
context.flush();
}
fn check_and_handle_sync(&mut self, context:&Context, sync:&WebGlSync) {
let data = self.data.as_ref().unwrap();
let status = context.get_sync_parameter(sync,Context::SYNC_STATUS);
fn check_and_handle_sync(&mut self, context: &Context, sync: &WebGlSync) {
let data = self.data.as_ref().unwrap();
let status = context.get_sync_parameter(sync, Context::SYNC_STATUS);
if status == Context::SIGNALED {
context.delete_sync(Some(sync));
self.sync = None;
let target = Context::PIXEL_PACK_BUFFER;
let offset = 0;
self.sync = None;
let target = Context::PIXEL_PACK_BUFFER;
let offset = 0;
let buffer_view = data.js_array.to_object();
context.bind_buffer(target,Some(&data.buffer));
context.get_buffer_sub_data_with_i32_and_array_buffer_view(target,offset,buffer_view);
context.bind_buffer(target, Some(&data.buffer));
context.get_buffer_sub_data_with_i32_and_array_buffer_view(target, offset, buffer_view);
if let Some(f) = &self.callback {
f(data.js_array.to_vec());
}
@ -151,15 +159,15 @@ impl<T:JsTypedArrayItem> PixelReadPass<T> {
}
}
impl<T:JsTypedArrayItem> pass::Definition for PixelReadPass<T> {
fn run(&mut self, instance:&pass::Instance) {
impl<T: JsTypedArrayItem> pass::Definition for PixelReadPass<T> {
fn run(&mut self, instance: &pass::Instance) {
if self.to_next_read > 0 {
self.to_next_read -= 1;
} else {
self.to_next_read = self.threshold;
self.init_if_fresh(&instance.context,&instance.variables);
self.init_if_fresh(&instance.context, &instance.variables);
if let Some(sync) = self.sync.clone() {
self.check_and_handle_sync(&instance.context,&sync);
self.check_and_handle_sync(&instance.context, &sync);
}
if self.sync.is_none() {
self.run_not_synced(&instance.context);

View File

@ -13,21 +13,21 @@ use crate::display::symbol::Screen;
// ========================
/// Renders the last `'color'` variable to the screen.
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
pub struct ScreenRenderPass {
screen: Screen,
}
impl ScreenRenderPass {
/// Constructor.
pub fn new(scene:&Scene) -> Self {
let screen = Screen::new_identity_painter(scene,"pass_color");
Self {screen}
pub fn new(scene: &Scene) -> Self {
let screen = Screen::new_identity_painter(scene, "pass_color");
Self { screen }
}
}
impl pass::Definition for ScreenRenderPass {
fn run(&mut self, _:&pass::Instance) {
fn run(&mut self, _: &pass::Instance) {
self.screen.render();
}
}

View File

@ -3,12 +3,12 @@
use crate::prelude::*;
use crate::display::render::pass;
use crate::display::scene::layer;
use crate::display::scene;
use crate::display::scene::layer;
use crate::display::scene::Scene;
use crate::display::symbol::registry::SymbolRegistry;
use crate::system::gpu::*;
use crate::display::symbol::MaskComposer;
use crate::system::gpu::*;
@ -16,142 +16,152 @@ use crate::display::symbol::MaskComposer;
// === SymbolsRenderPass ===
// =========================
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
struct Framebuffers {
composed : pass::Framebuffer,
mask : pass::Framebuffer,
layer : pass::Framebuffer,
composed: pass::Framebuffer,
mask: pass::Framebuffer,
layer: pass::Framebuffer,
}
impl Framebuffers {
fn new
( composed : pass::Framebuffer
, mask : pass::Framebuffer
, layer : pass::Framebuffer
) -> Self {
Self {composed,mask,layer}
fn new(composed: pass::Framebuffer, mask: pass::Framebuffer, layer: pass::Framebuffer) -> Self {
Self { composed, mask, layer }
}
}
/// Pass for rendering all symbols. The results are stored in the 'color' and 'id' outputs.
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
pub struct SymbolsRenderPass {
logger : Logger,
symbol_registry : SymbolRegistry,
layers : scene::HardcodedLayers,
framebuffers : Option<Framebuffers>,
scene : Scene,
mask_composer : MaskComposer,
logger: Logger,
symbol_registry: SymbolRegistry,
layers: scene::HardcodedLayers,
framebuffers: Option<Framebuffers>,
scene: Scene,
mask_composer: MaskComposer,
}
impl SymbolsRenderPass {
/// Constructor.
pub fn new
( logger : impl AnyLogger
, scene : &Scene
, symbol_registry : &SymbolRegistry
, layers : &scene::HardcodedLayers
pub fn new(
logger: impl AnyLogger,
scene: &Scene,
symbol_registry: &SymbolRegistry,
layers: &scene::HardcodedLayers,
) -> Self {
let logger = Logger::new_sub(logger,"SymbolsRenderPass");
let logger = Logger::new_sub(logger, "SymbolsRenderPass");
let symbol_registry = symbol_registry.clone_ref();
let layers = layers.clone_ref();
let framebuffers = default();
let scene = scene.clone_ref();
let mask_composer = MaskComposer::new
(&scene,"pass_mask_color","pass_layer_color","pass_layer_id");
Self {logger,symbol_registry,layers,framebuffers,scene,mask_composer}
let layers = layers.clone_ref();
let framebuffers = default();
let scene = scene.clone_ref();
let mask_composer =
MaskComposer::new(&scene, "pass_mask_color", "pass_layer_color", "pass_layer_id");
Self { logger, symbol_registry, layers, framebuffers, scene, mask_composer }
}
}
impl pass::Definition for SymbolsRenderPass {
fn initialize(&mut self, instance:&pass::Instance) {
let rgba = texture::Rgba;
let tex_type = texture::item_type::u8;
let id_params = texture::Parameters {
min_filter : texture::MinFilter::Nearest,
mag_filter : texture::MagFilter::Nearest,
fn initialize(&mut self, instance: &pass::Instance) {
let rgba = texture::Rgba;
let tex_type = texture::item_type::u8;
let id_params = texture::Parameters {
min_filter: texture::MinFilter::Nearest,
mag_filter: texture::MagFilter::Nearest,
..default()
};
let out_color = pass::OutputDefinition::new_rgba("color");
let out_id = pass::OutputDefinition::new("id",rgba,tex_type,id_params);
let tex_color = instance.new_screen_texture(&out_color);
let tex_id = instance.new_screen_texture(&out_id);
let composed_fb = instance.new_framebuffer(&[&tex_color,&tex_id]);
let out_color = pass::OutputDefinition::new_rgba("color");
let out_id = pass::OutputDefinition::new("id", rgba, tex_type, id_params);
let tex_color = instance.new_screen_texture(&out_color);
let tex_id = instance.new_screen_texture(&out_id);
let composed_fb = instance.new_framebuffer(&[&tex_color, &tex_id]);
let out_mask_color = pass::OutputDefinition::new_rgba("mask_color");
let out_mask_id = pass::OutputDefinition::new("mask_id",rgba,tex_type,id_params);
let out_mask_id = pass::OutputDefinition::new("mask_id", rgba, tex_type, id_params);
let tex_mask_color = instance.new_screen_texture(&out_mask_color);
let tex_mask_id = instance.new_screen_texture(&out_mask_id);
let mask_fb = instance.new_framebuffer(&[&tex_mask_color,&tex_mask_id]);
let tex_mask_id = instance.new_screen_texture(&out_mask_id);
let mask_fb = instance.new_framebuffer(&[&tex_mask_color, &tex_mask_id]);
let out_layer_color = pass::OutputDefinition::new_rgba("layer_color");
let out_layer_id = pass::OutputDefinition::new("layer_id",rgba,tex_type,id_params);
let out_layer_id = pass::OutputDefinition::new("layer_id", rgba, tex_type, id_params);
let tex_layer_color = instance.new_screen_texture(&out_layer_color);
let tex_layer_id = instance.new_screen_texture(&out_layer_id);
let layer_fb = instance.new_framebuffer(&[&tex_layer_color,&tex_layer_id]);
let tex_layer_id = instance.new_screen_texture(&out_layer_id);
let layer_fb = instance.new_framebuffer(&[&tex_layer_color, &tex_layer_id]);
self.framebuffers = Some(Framebuffers::new(composed_fb,mask_fb,layer_fb));
self.framebuffers = Some(Framebuffers::new(composed_fb, mask_fb, layer_fb));
}
fn run(&mut self, instance:&pass::Instance) {
fn run(&mut self, instance: &pass::Instance) {
let framebuffers = self.framebuffers.as_ref().unwrap();
framebuffers.composed.bind();
let arr = vec![0.0,0.0,0.0,0.0];
instance.context.clear_bufferfv_with_f32_array(Context::COLOR,0,&arr);
instance.context.clear_bufferfv_with_f32_array(Context::COLOR,1,&arr);
let arr = vec![0.0, 0.0, 0.0, 0.0];
instance.context.clear_bufferfv_with_f32_array(Context::COLOR, 0, &arr);
instance.context.clear_bufferfv_with_f32_array(Context::COLOR, 1, &arr);
let mut scissor_stack = default();
self.render_layer(instance,&self.layers.root.clone(),&mut scissor_stack,false);
self.render_layer(instance, &self.layers.root.clone(), &mut scissor_stack, false);
if !scissor_stack.is_empty() {
warning!(&self.logger,"The scissor stack was not cleaned properly. \
This is an internal bug that may lead to visual artifacts. Please report it.");
warning!(
&self.logger,
"The scissor stack was not cleaned properly. \
This is an internal bug that may lead to visual artifacts. Please report it."
);
}
instance.context.bind_framebuffer(Context::FRAMEBUFFER,None);
instance.context.bind_framebuffer(Context::FRAMEBUFFER, None);
}
}
impl SymbolsRenderPass {
fn enable_scissor_test(&self, instance:&pass::Instance) {
fn enable_scissor_test(&self, instance: &pass::Instance) {
instance.context.enable(web_sys::WebGl2RenderingContext::SCISSOR_TEST);
}
fn disable_scissor_test(&self, instance:&pass::Instance) {
fn disable_scissor_test(&self, instance: &pass::Instance) {
instance.context.disable(web_sys::WebGl2RenderingContext::SCISSOR_TEST);
}
fn render_layer(&mut self, instance:&pass::Instance, layer:&layer::Layer, scissor_stack:&mut Vec<layer::ScissorBox>, parent_masked:bool) {
let framebuffers = self.framebuffers.as_ref().unwrap();
let parent_scissor_box = scissor_stack.first().copied();
let layer_scissor_box = layer.scissor_box();
let scissor_box = parent_scissor_box.concat(layer_scissor_box);
let scissor_box_changed = layer_scissor_box.is_some();
let first_scissor_usage = scissor_box_changed && parent_scissor_box.is_none();
fn render_layer(
&mut self,
instance: &pass::Instance,
layer: &layer::Layer,
scissor_stack: &mut Vec<layer::ScissorBox>,
parent_masked: bool,
) {
let framebuffers = self.framebuffers.as_ref().unwrap();
let parent_scissor_box = scissor_stack.first().copied();
let layer_scissor_box = layer.scissor_box();
let scissor_box = parent_scissor_box.concat(layer_scissor_box);
let scissor_box_changed = layer_scissor_box.is_some();
let first_scissor_usage = scissor_box_changed && parent_scissor_box.is_none();
if let Some(scissor_box) = scissor_box {
if scissor_box_changed {
if first_scissor_usage { self.enable_scissor_test(instance) }
if first_scissor_usage {
self.enable_scissor_test(instance)
}
scissor_stack.push(scissor_box);
let position = scissor_box.position();
let size = scissor_box.size();
instance.context.scissor(position.x,position.y,size.x,size.y);
let size = scissor_box.size();
instance.context.scissor(position.x, position.y, size.x, size.y);
}
}
let layer_mask = layer.mask();
let is_masked = layer_mask.is_some();
let layer_mask = layer.mask();
let is_masked = layer_mask.is_some();
let was_ever_masked = is_masked || parent_masked;
let nested_masking = is_masked && parent_masked;
let nested_masking = is_masked && parent_masked;
if nested_masking {
warning!(&self.logger,"Nested layer masking is not supported yet. Skipping nested masks.");
warning!(
&self.logger,
"Nested layer masking is not supported yet. Skipping nested masks."
);
} else if let Some(mask) = layer_mask {
framebuffers.mask.bind();
let arr = vec![0.0, 0.0, 0.0, 0.0];
instance.context.clear_bufferfv_with_f32_array(Context::COLOR, 0, &arr);
instance.context.clear_bufferfv_with_f32_array(Context::COLOR, 1, &arr);
self.render_layer(instance,&mask,scissor_stack,was_ever_masked);
self.render_layer(instance, &mask, scissor_stack, was_ever_masked);
let framebuffers = self.framebuffers.as_ref().unwrap();
framebuffers.layer.bind();
@ -163,7 +173,7 @@ impl SymbolsRenderPass {
self.symbol_registry.set_camera(&layer.camera());
self.symbol_registry.render_by_ids(&layer.symbols());
for sublayer in layer.sublayers().iter() {
self.render_layer(instance,sublayer,scissor_stack,was_ever_masked);
self.render_layer(instance, sublayer, scissor_stack, was_ever_masked);
}
if is_masked {
@ -174,7 +184,9 @@ impl SymbolsRenderPass {
if scissor_box_changed {
scissor_stack.pop();
if first_scissor_usage { self.disable_scissor_test(instance) }
if first_scissor_usage {
self.disable_scissor_test(instance)
}
}
}
}

View File

@ -30,9 +30,9 @@ impl {
}
}}
impl<Pass:pass::Definition> Add<Pass> for Pipeline {
impl<Pass: pass::Definition> Add<Pass> for Pipeline {
type Output = Self;
fn add(self, pass:Pass) -> Self::Output {
fn add(self, pass: Pass) -> Self::Output {
let pass = Box::new(pass);
self.rc.borrow_mut().passes.push(pass);
self

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,12 @@
use crate::prelude::*;
use crate::display::object::traits::*;
use crate::display::camera::Camera2d;
use crate::display::camera::camera2d::Projection;
use crate::display::symbol::DomSymbol;
use crate::display::camera::Camera2d;
use crate::display::object::traits::*;
use crate::display::symbol::dom::eps;
use crate::display::symbol::dom::inverse_y_translation;
use crate::display::symbol::DomSymbol;
use crate::system::gpu::data::JsBufferView;
use crate::system::web;
use crate::system::web::NodeInserter;
@ -52,34 +52,37 @@ mod js {
/// Setup Camera orthographic projection on DOM.
#[allow(unsafe_code)]
pub fn setup_camera_orthographic(dom:&web::JsValue, matrix_array:&web::JsValue);
pub fn setup_camera_orthographic(dom: &web::JsValue, matrix_array: &web::JsValue);
/// Setup Camera perspective projection on DOM.
#[allow(unsafe_code)]
pub fn setup_camera_perspective
(dom:&web::JsValue, near:&web::JsValue, matrix_array:&web::JsValue);
pub fn setup_camera_perspective(
dom: &web::JsValue,
near: &web::JsValue,
matrix_array: &web::JsValue,
);
}
}
#[allow(unsafe_code)]
fn setup_camera_perspective(dom:&web::JsValue, near:f32, matrix:&Matrix4<f32>) {
fn setup_camera_perspective(dom: &web::JsValue, near: f32, matrix: &Matrix4<f32>) {
// Views to WASM memory are only valid as long the backing buffer isn't
// resized. Check documentation of IntoFloat32ArrayView trait for more
// details.
unsafe {
let matrix_array = matrix.js_buffer_view();
js::setup_camera_perspective(dom,&near.into(),&matrix_array)
js::setup_camera_perspective(dom, &near.into(), &matrix_array)
}
}
#[allow(unsafe_code)]
fn setup_camera_orthographic(dom:&web::JsValue, matrix:&Matrix4<f32>) {
fn setup_camera_orthographic(dom: &web::JsValue, matrix: &Matrix4<f32>) {
// Views to WASM memory are only valid as long the backing buffer isn't
// resized. Check documentation of IntoFloat32ArrayView trait for more
// details.
unsafe {
let matrix_array = matrix.js_buffer_view();
js::setup_camera_orthographic(dom,&matrix_array)
js::setup_camera_orthographic(dom, &matrix_array)
}
}
@ -92,7 +95,7 @@ fn setup_camera_orthographic(dom:&web::JsValue, matrix:&Matrix4<f32>) {
/// Inverts Matrix Y coordinates. It's equivalent to scaling by (1.0, -1.0, 1.0).
pub fn invert_y(mut m: Matrix4<f32>) -> Matrix4<f32> {
// Negating the second column to invert Y.
m.row_part_mut(1,4).iter_mut().for_each(|a| *a = -*a);
m.row_part_mut(1, 4).iter_mut().for_each(|a| *a = -*a);
m
}
@ -103,19 +106,19 @@ pub fn invert_y(mut m: Matrix4<f32>) -> Matrix4<f32> {
// ====================
/// Internal representation for `DomScene`.
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
pub struct DomSceneData {
/// The root dom element of this scene.
pub dom : HtmlDivElement,
pub dom: HtmlDivElement,
/// The child div of the `dom` element with view-projection Css 3D transformations applied.
pub view_projection_dom : HtmlDivElement,
logger : Logger
pub view_projection_dom: HtmlDivElement,
logger: Logger,
}
impl DomSceneData {
/// Constructor.
pub fn new(dom:HtmlDivElement, view_projection_dom:HtmlDivElement, logger:Logger) -> Self {
Self {dom,view_projection_dom,logger}
pub fn new(dom: HtmlDivElement, view_projection_dom: HtmlDivElement, logger: Logger) -> Self {
Self { dom, view_projection_dom, logger }
}
}
@ -132,37 +135,37 @@ impl DomSceneData {
/// To make use of its functionalities, the API user can create a `Css3dSystem` by using
/// the `DomScene::new_system` method which creates and manages instances of
/// `DomSymbol`s.
#[derive(Clone,CloneRef,Debug,Shrinkwrap)]
#[derive(Clone, CloneRef, Debug, Shrinkwrap)]
pub struct DomScene {
data : Rc<DomSceneData>,
data: Rc<DomSceneData>,
}
impl DomScene {
/// Constructor.
pub fn new(logger:impl AnyLogger) -> Self {
let logger = Logger::new_sub(logger,"DomScene");
let dom = web::create_div();
pub fn new(logger: impl AnyLogger) -> Self {
let logger = Logger::new_sub(logger, "DomScene");
let dom = web::create_div();
let view_projection_dom = web::create_div();
dom.set_class_name("dom-scene-layer");
dom.set_style_or_warn("position" , "absolute" , &logger);
dom.set_style_or_warn("top" , "0px" , &logger);
dom.set_style_or_warn("overflow" , "hidden" , &logger);
dom.set_style_or_warn("overflow" , "hidden" , &logger);
dom.set_style_or_warn("width" , "100%" , &logger);
dom.set_style_or_warn("height" , "100%" , &logger);
dom.set_style_or_warn("pointer-events" , "none" , &logger);
dom.set_style_or_warn("position", "absolute", &logger);
dom.set_style_or_warn("top", "0px", &logger);
dom.set_style_or_warn("overflow", "hidden", &logger);
dom.set_style_or_warn("overflow", "hidden", &logger);
dom.set_style_or_warn("width", "100%", &logger);
dom.set_style_or_warn("height", "100%", &logger);
dom.set_style_or_warn("pointer-events", "none", &logger);
view_projection_dom.set_class_name("view_projection");
view_projection_dom.set_style_or_warn("width" , "100%" , &logger);
view_projection_dom.set_style_or_warn("height" , "100%" , &logger);
view_projection_dom.set_style_or_warn("transform-style" , "preserve-3d" , &logger);
view_projection_dom.set_style_or_warn("width", "100%", &logger);
view_projection_dom.set_style_or_warn("height", "100%", &logger);
view_projection_dom.set_style_or_warn("transform-style", "preserve-3d", &logger);
dom.append_or_warn(&view_projection_dom,&logger);
dom.append_or_warn(&view_projection_dom, &logger);
let data = DomSceneData::new (dom,view_projection_dom,logger);
let data = DomSceneData::new(dom, view_projection_dom, logger);
let data = Rc::new(data);
Self {data}
Self { data }
}
/// Gets the number of children DomSymbols.
@ -171,19 +174,19 @@ impl DomScene {
}
/// Sets the z-index of this DOM element.
pub fn set_z_index(&self, z:i32) {
pub fn set_z_index(&self, z: i32) {
self.data.dom.set_style_or_warn("z-index", z.to_string(), &self.logger);
}
/// Sets the CSS property `filter: grayscale({value})` on this element. A value of 0.0 displays
/// the element normally. A value of 1.0 will make the element completely gray.
pub fn filter_grayscale(&self, value:f32) {
self.data.dom.set_style_or_warn("filter",format!("grayscale({})",value),&self.logger);
pub fn filter_grayscale(&self, value: f32) {
self.data.dom.set_style_or_warn("filter", format!("grayscale({})", value), &self.logger);
}
/// Creates a new instance of DomSymbol and adds it to parent.
pub fn manage(&self, object:&DomSymbol) {
let dom = object.dom();
pub fn manage(&self, object: &DomSymbol) {
let dom = object.dom();
let data = &self.data;
if object.is_visible() {
self.view_projection_dom.append_or_panic(dom);
@ -196,24 +199,26 @@ impl DomScene {
/// Update the objects to match the new camera's point of view. This function should be called
/// only after camera position change.
pub fn update_view_projection(&self, camera:&Camera2d) {
if self.children_number() == 0 { return }
pub fn update_view_projection(&self, camera: &Camera2d) {
if self.children_number() == 0 {
return;
}
let trans_cam = camera.transform_matrix().try_inverse();
let trans_cam = trans_cam.expect("Camera's matrix is not invertible.");
let trans_cam = trans_cam.map(eps);
let trans_cam = inverse_y_translation(trans_cam);
let half_dim = camera.screen().height / 2.0;
let trans_cam = camera.transform_matrix().try_inverse();
let trans_cam = trans_cam.expect("Camera's matrix is not invertible.");
let trans_cam = trans_cam.map(eps);
let trans_cam = inverse_y_translation(trans_cam);
let half_dim = camera.screen().height / 2.0;
let fovy_slope = camera.half_fovy_slope();
let near = half_dim / fovy_slope;
let near = half_dim / fovy_slope;
match camera.projection() {
Projection::Perspective{..} => {
js::setup_perspective(&self.data.dom,&near.into());
setup_camera_perspective(&self.data.view_projection_dom,near,&trans_cam);
},
Projection::Perspective { .. } => {
js::setup_perspective(&self.data.dom, &near.into());
setup_camera_perspective(&self.data.view_projection_dom, near, &trans_cam);
}
Projection::Orthographic => {
setup_camera_orthographic(&self.data.view_projection_dom,&trans_cam);
setup_camera_orthographic(&self.data.view_projection_dom, &trans_cam);
}
}
}

View File

@ -3,17 +3,17 @@
use crate::data::dirty::traits::*;
use crate::prelude::*;
use crate::data::OptVec;
use crate::data::dirty;
use crate::data::OptVec;
use crate::display;
use crate::display::camera::Camera2d;
use crate::display::scene::Scene;
use crate::display::shape::ShapeSystemInstance;
use crate::display::shape::system::DynShapeSystemInstance;
use crate::display::shape::system::DynShapeSystemOf;
use crate::display::shape::system::KnownShapeSystemId;
use crate::display::shape::system::ShapeSystemId;
use crate::display::shape::ShapeSystemInstance;
use crate::display::symbol::SymbolId;
use crate::display;
use crate::system::gpu::data::attribute;
use enso_data::dependency_graph::DependencyGraph;
@ -28,9 +28,9 @@ use std::any::TypeId;
// =============
/// Display layers implementation. Layer consist of a [`Camera`] and a set of [`LayerItem`]s. Layers
/// are hierarchical and contain sublayers. Items of a layer containing sublayers layers are displayed
/// below items of sublayers layers. Layers are allowed to share references to the same camera. and
/// the same [`Symbol`]s.
/// are hierarchical and contain sublayers. Items of a layer containing sublayers layers are
/// displayed below items of sublayers layers. Layers are allowed to share references to the same
/// camera. and the same [`Symbol`]s.
///
///
/// # Symbol Management
@ -74,10 +74,10 @@ use std::any::TypeId;
/// # Symbols Ordering
/// There are two ways to define symbol ordering in scene layers, a global, and local (per-layer)
/// one. In order to define a global depth-order dependency, you can use the
/// `add_elements_order_dependency`, and the `remove_elements_order_dependency` methods respectively.
/// In order to define local (per-layer) depth-order dependency, you can use methods of the same
/// names in every layer instance. After changing a dependency graph, the layer management marks
/// appropriate dirty flags and re-orders symbols on each new frame processed.
/// `add_elements_order_dependency`, and the `remove_elements_order_dependency` methods
/// respectively. In order to define local (per-layer) depth-order dependency, you can use methods
/// of the same names in every layer instance. After changing a dependency graph, the layer
/// management marks appropriate dirty flags and re-orders symbols on each new frame processed.
///
/// During symbol sorting, the global and local dependency graphs are merged together. The defined
/// rules are equivalently important, so local rules will not override global ones. In case of
@ -122,9 +122,9 @@ use std::any::TypeId;
/// # Layer Lifetime Management
/// Both [`Group`] and every [`Layer`] instance are strongly interconnected. This is needed for a
/// nice API. For example, [`Layer`] allows you to add symbols while removing them from other layers
/// automatically. Although the [`SublayersModel`] registers [`WeakLayer`], the weak form is used only
/// to break cycles and never points to a dropped [`Layer`], as layers update the information on
/// a drop.
/// automatically. Although the [`SublayersModel`] registers [`WeakLayer`], the weak form is used
/// only to break cycles and never points to a dropped [`Layer`], as layers update the information
/// on a drop.
///
/// # Masking Layers With ScissorBox
/// Layers rendering an be limited to a specific set of pixels by using the [`ScissorBox`] object.
@ -152,9 +152,9 @@ use std::any::TypeId;
/// Please note that the current implementation does not allow for hierarchical masks (masks applied
/// to already masked area or masks applied to masks). If you try using masks in hierarchical way,
/// the nested masks will be skipped and a warning will be emitted to the console.
#[derive(Clone,CloneRef)]
#[derive(Clone, CloneRef)]
pub struct Layer {
model : Rc<LayerModel>
model: Rc<LayerModel>,
}
impl Deref for Layer {
@ -166,20 +166,20 @@ impl Deref for Layer {
impl Debug for Layer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&*self.model,f)
Debug::fmt(&*self.model, f)
}
}
impl Layer {
/// Constructor.
pub fn new(logger:Logger) -> Self {
pub fn new(logger: Logger) -> Self {
let model = LayerModel::new(logger);
let model = Rc::new(model);
Self {model}
Self { model }
}
/// Constructor.
pub fn new_with_cam(logger:Logger, camera:&Camera2d) -> Self {
pub fn new_with_cam(logger: Logger, camera: &Camera2d) -> Self {
let this = Self::new(logger);
this.set_camera(camera);
this
@ -188,32 +188,31 @@ impl Layer {
/// Create a new weak pointer to this layer.
pub fn downgrade(&self) -> WeakLayer {
let model = Rc::downgrade(&self.model);
WeakLayer {model}
WeakLayer { model }
}
/// Add the display object to this layer and remove it from any other layers.
pub fn add_exclusive(&self, object:impl display::Object) {
pub fn add_exclusive(&self, object: impl display::Object) {
object.display_object().add_to_display_layer_exclusive(self);
}
/// Instantiate the provided [`DynamicShape`].
pub fn instantiate<T>
(&self, scene:&Scene, shape:&T) -> LayerDynamicShapeInstance
where T : display::shape::system::DynamicShape {
let (shape_system_info,symbol_id,instance_id) =
self.shape_system_registry.instantiate(scene,shape);
self.add_shape(shape_system_info,symbol_id);
LayerDynamicShapeInstance::new(self,symbol_id,instance_id)
pub fn instantiate<T>(&self, scene: &Scene, shape: &T) -> LayerDynamicShapeInstance
where T: display::shape::system::DynamicShape {
let (shape_system_info, symbol_id, instance_id) =
self.shape_system_registry.instantiate(scene, shape);
self.add_shape(shape_system_info, symbol_id);
LayerDynamicShapeInstance::new(self, symbol_id, instance_id)
}
/// Iterate over all layers and sublayers of this layer hierarchically. Parent layers will be
/// visited before their corresponding sublayers. Does not visit masks. If you want to visit
/// masks, use [`iter_sublayers_and_masks_nested`] instead.
pub fn iter_sublayers_nested(&self, f:impl Fn(&Layer)) {
pub fn iter_sublayers_nested(&self, f: impl Fn(&Layer)) {
self.iter_sublayers_nested_internal(&f)
}
fn iter_sublayers_nested_internal(&self, f:&impl Fn(&Layer)) {
fn iter_sublayers_nested_internal(&self, f: &impl Fn(&Layer)) {
f(self);
for layer in self.sublayers() {
layer.iter_sublayers_nested_internal(f)
@ -222,11 +221,11 @@ impl Layer {
/// Iterate over all layers, sublayers, masks, and their sublayers of this layer hierarchically.
/// Parent layers will be visited before their corresponding sublayers.
pub fn iter_sublayers_and_masks_nested(&self, f:impl Fn(&Layer)) {
pub fn iter_sublayers_and_masks_nested(&self, f: impl Fn(&Layer)) {
self.iter_sublayers_and_masks_nested_internal(&f)
}
fn iter_sublayers_and_masks_nested_internal(&self, f:&impl Fn(&Layer)) {
fn iter_sublayers_and_masks_nested_internal(&self, f: &impl Fn(&Layer)) {
f(self);
if let Some(mask) = &*self.mask.borrow() {
if let Some(layer) = mask.upgrade() {
@ -240,7 +239,7 @@ impl Layer {
}
impl From<&Layer> for LayerId {
fn from(t:&Layer) -> Self {
fn from(t: &Layer) -> Self {
t.id()
}
}
@ -252,27 +251,27 @@ impl From<&Layer> for LayerId {
// =================
/// A weak version of [`Layer`].
#[derive(Clone,CloneRef)]
#[derive(Clone, CloneRef)]
pub struct WeakLayer {
model : Weak<LayerModel>
model: Weak<LayerModel>,
}
impl WeakLayer {
/// Upgrade to strong reference.
pub fn upgrade(&self) -> Option<Layer> {
self.model.upgrade().map(|model| Layer {model})
self.model.upgrade().map(|model| Layer { model })
}
}
impl Debug for WeakLayer {
fn fmt(&self, f:&mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,"WeakLayer")
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "WeakLayer")
}
}
impl Eq for WeakLayer {}
impl PartialEq for WeakLayer {
fn eq(&self, other:&Self) -> bool {
fn eq(&self, other: &Self) -> bool {
self.model.ptr_eq(&other.model)
}
}
@ -289,21 +288,21 @@ impl PartialEq for WeakLayer {
#[derive(Clone)]
#[allow(missing_docs)]
pub struct LayerModel {
logger : Logger,
pub camera : RefCell<Camera2d>,
pub shape_system_registry : ShapeSystemRegistry,
shape_system_to_symbol_info_map : RefCell<HashMap<ShapeSystemId,ShapeSystemSymbolInfo>>,
symbol_to_shape_system_map : RefCell<HashMap<SymbolId,ShapeSystemId>>,
elements : RefCell<BTreeSet<LayerItem>>,
symbols_ordered : RefCell<Vec<SymbolId>>,
depth_order : RefCell<DependencyGraph<LayerItem>>,
depth_order_dirty : dirty::SharedBool<OnDepthOrderDirty>,
parents : Rc<RefCell<Vec<Sublayers>>>,
global_element_depth_order : Rc<RefCell<DependencyGraph<LayerItem>>>,
sublayers : Sublayers,
mask : RefCell<Option<WeakLayer>>,
scissor_box : RefCell<Option<ScissorBox>>,
mem_mark : Rc<()>,
logger: Logger,
pub camera: RefCell<Camera2d>,
pub shape_system_registry: ShapeSystemRegistry,
shape_system_to_symbol_info_map: RefCell<HashMap<ShapeSystemId, ShapeSystemSymbolInfo>>,
symbol_to_shape_system_map: RefCell<HashMap<SymbolId, ShapeSystemId>>,
elements: RefCell<BTreeSet<LayerItem>>,
symbols_ordered: RefCell<Vec<SymbolId>>,
depth_order: RefCell<DependencyGraph<LayerItem>>,
depth_order_dirty: dirty::SharedBool<OnDepthOrderDirty>,
parents: Rc<RefCell<Vec<Sublayers>>>,
global_element_depth_order: Rc<RefCell<DependencyGraph<LayerItem>>>,
sublayers: Sublayers,
mask: RefCell<Option<WeakLayer>>,
scissor_box: RefCell<Option<ScissorBox>>,
mem_mark: Rc<()>,
}
impl Debug for LayerModel {
@ -328,26 +327,40 @@ impl Drop for LayerModel {
}
impl LayerModel {
fn new(logger:Logger) -> Self {
let logger_dirty = Logger::new_sub(&logger,"dirty");
let camera = RefCell::new(Camera2d::new(&logger));
let shape_system_registry = default();
fn new(logger: Logger) -> Self {
let logger_dirty = Logger::new_sub(&logger, "dirty");
let camera = RefCell::new(Camera2d::new(&logger));
let shape_system_registry = default();
let shape_system_to_symbol_info_map = default();
let symbol_to_shape_system_map = default();
let elements = default();
let symbols_ordered = default();
let depth_order = default();
let parents = default();
let on_mut = on_depth_order_dirty(&parents);
let depth_order_dirty = dirty::SharedBool::new(logger_dirty,on_mut);
let global_element_depth_order = default();
let sublayers = Sublayers::new(Logger::new_sub(&logger,"registry"));
let mask = default();
let scissor_box = default();
let mem_mark = default();
Self {logger,camera,shape_system_registry,shape_system_to_symbol_info_map
,symbol_to_shape_system_map,elements,symbols_ordered,depth_order,depth_order_dirty
,parents,global_element_depth_order,sublayers,mask,scissor_box,mem_mark}
let symbol_to_shape_system_map = default();
let elements = default();
let symbols_ordered = default();
let depth_order = default();
let parents = default();
let on_mut = on_depth_order_dirty(&parents);
let depth_order_dirty = dirty::SharedBool::new(logger_dirty, on_mut);
let global_element_depth_order = default();
let sublayers = Sublayers::new(Logger::new_sub(&logger, "registry"));
let mask = default();
let scissor_box = default();
let mem_mark = default();
Self {
logger,
camera,
shape_system_registry,
shape_system_to_symbol_info_map,
symbol_to_shape_system_map,
elements,
symbols_ordered,
depth_order,
depth_order_dirty,
parents,
global_element_depth_order,
sublayers,
mask,
scissor_box,
mem_mark,
}
}
/// Unique identifier of this layer. It is memory-based, it will be unique even for layers in
@ -366,32 +379,40 @@ impl LayerModel {
/// Return the [`SymbolId`] of the provided [`LayerItem`] if it was added to the current
/// layer.
pub fn symbol_id_of_element(&self, element:LayerItem) -> Option<SymbolId> {
pub fn symbol_id_of_element(&self, element: LayerItem) -> Option<SymbolId> {
use LayerItem::*;
match element {
Symbol(id) => Some(id),
ShapeSystem(id) => self.shape_system_to_symbol_info_map.borrow().get(&id).map(|t|t.id)
Symbol(id) => Some(id),
ShapeSystem(id) => self.shape_system_to_symbol_info_map.borrow().get(&id).map(|t| t.id),
}
}
/// Add depth-order dependency between two [`LayerItem`]s in this layer.
pub fn add_elements_order_dependency
(&self, below:impl Into<LayerItem>, above:impl Into<LayerItem>) {
pub fn add_elements_order_dependency(
&self,
below: impl Into<LayerItem>,
above: impl Into<LayerItem>,
) {
let below = below.into();
let above = above.into();
if self.depth_order.borrow_mut().insert_dependency(below,above) {
if self.depth_order.borrow_mut().insert_dependency(below, above) {
self.depth_order_dirty.set();
}
}
/// Remove a depth-order dependency between two [`LayerItem`]s in this layer. Returns `true`
/// if the dependency was found, and `false` otherwise.
pub fn remove_elements_order_dependency
(&self, below:impl Into<LayerItem>, above:impl Into<LayerItem>) -> bool {
pub fn remove_elements_order_dependency(
&self,
below: impl Into<LayerItem>,
above: impl Into<LayerItem>,
) -> bool {
let below = below.into();
let above = above.into();
let found = self.depth_order.borrow_mut().remove_dependency(below,above);
if found { self.depth_order_dirty.set(); }
let found = self.depth_order.borrow_mut().remove_dependency(below, above);
if found {
self.depth_order_dirty.set();
}
found
}
@ -401,14 +422,15 @@ impl LayerModel {
/// # Future Improvements
/// This implementation can be simplified to `S1:KnownShapeSystemId` (not using [`Content`] at
/// all), after the compiler gets updated to newer version.
pub fn add_shapes_order_dependency<S1,S2>(&self) -> (PhantomData<S1>,PhantomData<S2>)
where S1 : HasContent,
S2 : HasContent,
Content<S1> : KnownShapeSystemId,
Content<S2> : KnownShapeSystemId {
pub fn add_shapes_order_dependency<S1, S2>(&self) -> (PhantomData<S1>, PhantomData<S2>)
where
S1: HasContent,
S2: HasContent,
Content<S1>: KnownShapeSystemId,
Content<S2>: KnownShapeSystemId, {
let s1_id = <Content<S1>>::shape_system_id();
let s2_id = <Content<S2>>::shape_system_id();
self.add_elements_order_dependency(s1_id,s2_id);
self.add_elements_order_dependency(s1_id, s2_id);
self.depth_order_dirty.set();
default()
}
@ -420,17 +442,21 @@ impl LayerModel {
/// # Future Improvements
/// This implementation can be simplified to `S1:KnownShapeSystemId` (not using [`Content`] at
/// all), after the compiler gets updated to newer version.
pub fn remove_shapes_order_dependency<S1,S2>
(&self) -> (bool,PhantomData<S1>,PhantomData<S2>)
where S1 : HasContent,
S2 : HasContent,
Content<S1> : KnownShapeSystemId,
Content<S2> : KnownShapeSystemId {
pub fn remove_shapes_order_dependency<S1, S2>(
&self,
) -> (bool, PhantomData<S1>, PhantomData<S2>)
where
S1: HasContent,
S2: HasContent,
Content<S1>: KnownShapeSystemId,
Content<S2>: KnownShapeSystemId, {
let s1_id = <Content<S1>>::shape_system_id();
let s2_id = <Content<S2>>::shape_system_id();
let found = self.remove_elements_order_dependency(s1_id,s2_id);
if found { self.depth_order_dirty.set(); }
(found,default(),default())
let found = self.remove_elements_order_dependency(s1_id, s2_id);
if found {
self.depth_order_dirty.set();
}
(found, default(), default())
}
/// Camera getter of this layer.
@ -439,56 +465,63 @@ impl LayerModel {
}
/// Camera setter of this layer.
pub fn set_camera(&self, camera:impl Into<Camera2d>) {
pub fn set_camera(&self, camera: impl Into<Camera2d>) {
let camera = camera.into();
*self.camera.borrow_mut() = camera;
}
/// Add the symbol to this layer.
pub fn add_symbol(&self, symbol_id:impl Into<SymbolId>) {
self.add_element(symbol_id.into(),None)
pub fn add_symbol(&self, symbol_id: impl Into<SymbolId>) {
self.add_element(symbol_id.into(), None)
}
/// Add the shape to this layer.
pub(crate) fn add_shape
(&self, shape_system_info:ShapeSystemInfo, symbol_id:impl Into<SymbolId>) {
self.add_element(symbol_id.into(),Some(shape_system_info))
pub(crate) fn add_shape(
&self,
shape_system_info: ShapeSystemInfo,
symbol_id: impl Into<SymbolId>,
) {
self.add_element(symbol_id.into(), Some(shape_system_info))
}
/// Internal helper for adding elements to this layer.
fn add_element(&self, symbol_id:SymbolId, shape_system_info:Option<ShapeSystemInfo>) {
fn add_element(&self, symbol_id: SymbolId, shape_system_info: Option<ShapeSystemInfo>) {
self.depth_order_dirty.set();
match shape_system_info {
None => { self.elements.borrow_mut().insert(LayerItem::Symbol(symbol_id)); }
None => {
self.elements.borrow_mut().insert(LayerItem::Symbol(symbol_id));
}
Some(info) => {
let symbol_info = ShapeSystemSymbolInfo::new(symbol_id,info.ordering);
self.shape_system_to_symbol_info_map.borrow_mut().insert(info.id,symbol_info);
self.symbol_to_shape_system_map.borrow_mut().insert(symbol_id,info.id);
let symbol_info = ShapeSystemSymbolInfo::new(symbol_id, info.ordering);
self.shape_system_to_symbol_info_map.borrow_mut().insert(info.id, symbol_info);
self.symbol_to_shape_system_map.borrow_mut().insert(symbol_id, info.id);
self.elements.borrow_mut().insert(LayerItem::ShapeSystem(info.id));
}
}
}
/// Remove the symbol from the current layer.
pub fn remove_symbol(&self, symbol_id:impl Into<SymbolId>) {
pub fn remove_symbol(&self, symbol_id: impl Into<SymbolId>) {
self.depth_order_dirty.set();
let symbol_id = symbol_id.into();
self.elements.borrow_mut().remove(&LayerItem::Symbol(symbol_id));
if let Some(shape_system_id) =
self.symbol_to_shape_system_map.borrow_mut().remove(&symbol_id) {
self.shape_system_to_symbol_info_map.borrow_mut().remove(&shape_system_id);
self.elements.borrow_mut().remove(&LayerItem::ShapeSystem(shape_system_id));
self.symbol_to_shape_system_map.borrow_mut().remove(&symbol_id)
{
self.shape_system_to_symbol_info_map.borrow_mut().remove(&shape_system_id);
self.elements.borrow_mut().remove(&LayerItem::ShapeSystem(shape_system_id));
}
}
/// Remove the [`ShapeSystem`] registered in this layer together with all of its [`Symbol`]s.
pub fn remove_shape_system(&self, shape_system_id:ShapeSystemId) {
pub fn remove_shape_system(&self, shape_system_id: ShapeSystemId) {
self.depth_order_dirty.set();
self.elements.borrow_mut().remove(&LayerItem::ShapeSystem(shape_system_id));
if let Some(symbol_id) =
self.shape_system_to_symbol_info_map.borrow_mut().remove(&shape_system_id) {
self.symbol_to_shape_system_map.borrow_mut().remove(&symbol_id.id);
self.shape_system_to_symbol_info_map.borrow_mut().remove(&shape_system_id)
{
self.symbol_to_shape_system_map.borrow_mut().remove(&symbol_id.id);
}
}
@ -498,8 +531,10 @@ impl LayerModel {
}
/// Consume all dirty flags and update the ordering of elements if needed.
pub(crate) fn update_internal
(&self, global_element_depth_order:Option<&DependencyGraph<LayerItem>>) {
pub(crate) fn update_internal(
&self,
global_element_depth_order: Option<&DependencyGraph<LayerItem>>,
) {
if self.depth_order_dirty.check() {
self.depth_order_dirty.unset();
self.depth_sort(global_element_depth_order);
@ -522,9 +557,10 @@ impl LayerModel {
/// dependency graph (from [`Group`]), the local one (per layer), and individual shape
/// preferences (see the "Compile Time Shapes Ordering Relations" section in docs of [`Group`]
/// to learn more).
fn combined_depth_order_graph
(&self, global_element_depth_order:Option<&DependencyGraph<LayerItem>>)
-> DependencyGraph<LayerItem> {
fn combined_depth_order_graph(
&self,
global_element_depth_order: Option<&DependencyGraph<LayerItem>>,
) -> DependencyGraph<LayerItem> {
let mut graph = if let Some(global_element_depth_order) = global_element_depth_order {
let mut graph = global_element_depth_order.clone();
graph.extend(self.depth_order.borrow().clone().into_iter());
@ -535,32 +571,38 @@ impl LayerModel {
for element in &*self.elements.borrow() {
if let LayerItem::ShapeSystem(id) = element {
if let Some(info) = self.shape_system_to_symbol_info_map.borrow().get(id) {
for &id2 in &info.below { graph.insert_dependency(*element,id2.into()); }
for &id2 in &info.above { graph.insert_dependency(id2.into(),*element); }
for &id2 in &info.below {
graph.insert_dependency(*element, id2.into());
}
for &id2 in &info.above {
graph.insert_dependency(id2.into(), *element);
}
}
}
};
}
graph
}
fn depth_sort(&self, global_element_depth_order:Option<&DependencyGraph<LayerItem>>) {
let graph = self.combined_depth_order_graph(global_element_depth_order);
fn depth_sort(&self, global_element_depth_order: Option<&DependencyGraph<LayerItem>>) {
let graph = self.combined_depth_order_graph(global_element_depth_order);
let elements_sorted = self.elements.borrow().iter().copied().collect_vec();
let sorted_elements = graph.into_unchecked_topo_sort(elements_sorted);
let sorted_symbols = sorted_elements.into_iter().filter_map(|element| {
match element {
let sorted_symbols = sorted_elements
.into_iter()
.filter_map(|element| match element {
LayerItem::Symbol(symbol_id) => Some(symbol_id),
LayerItem::ShapeSystem(id) => {
let out = self.shape_system_to_symbol_info_map.borrow().get(&id).map(|t|t.id);
let out = self.shape_system_to_symbol_info_map.borrow().get(&id).map(|t| t.id);
if out.is_none() {
warning!(self.logger,
warning!(
self.logger,
"Trying to perform depth-order of non-existing element '{id:?}'."
)
}
out
}
}
}).collect();
})
.collect();
*self.symbols_ordered.borrow_mut() = sorted_symbols;
}
}
@ -570,7 +612,7 @@ impl LayerModel {
impl LayerModel {
/// Query [`Layer`] by [`LayerId`].
pub fn get_sublayer(&self, layer_id:LayerId) -> Option<Layer> {
pub fn get_sublayer(&self, layer_id: LayerId) -> Option<Layer> {
self.sublayers.borrow().get(layer_id)
}
@ -582,9 +624,9 @@ impl LayerModel {
self.sublayers.borrow().all()
}
fn add_sublayer(&self, layer:&Layer) {
fn add_sublayer(&self, layer: &Layer) {
let ix = self.sublayers.borrow_mut().layers.insert(layer.downgrade());
self.sublayers.borrow_mut().layer_placement.insert(layer.id(),ix);
self.sublayers.borrow_mut().layer_placement.insert(layer.id(), ix);
layer.add_parent(&self.sublayers);
}
@ -597,12 +639,12 @@ impl LayerModel {
mem::take(&mut *self.sublayers.model.borrow_mut());
}
fn add_parent(&self, parent:&Sublayers) {
fn add_parent(&self, parent: &Sublayers) {
let parent = parent.clone_ref();
self.parents.borrow_mut().push(parent);
}
fn remove_parent(&self, parent:&Sublayers) {
fn remove_parent(&self, parent: &Sublayers) {
self.parents.borrow_mut().remove_item(parent);
}
@ -616,7 +658,7 @@ impl LayerModel {
}
/// Set all sublayers layer of this layer. Old sublayers layers will be unregistered.
pub fn set_sublayers(&self, layers:&[&Layer]) {
pub fn set_sublayers(&self, layers: &[&Layer]) {
self.remove_all_sublayers();
for layer in layers {
self.add_sublayer(layer)
@ -625,11 +667,11 @@ impl LayerModel {
/// The layer's mask, if any.
pub fn mask(&self) -> Option<Layer> {
self.mask.borrow().as_ref().and_then(|t|t.upgrade())
self.mask.borrow().as_ref().and_then(|t| t.upgrade())
}
/// Set a mask layer of this layer. Old mask layer will be unregistered.
pub fn set_mask(&self, mask:&Layer) {
pub fn set_mask(&self, mask: &Layer) {
self.remove_mask();
*self.mask.borrow_mut() = Some(mask.downgrade());
mask.add_parent(&self.sublayers);
@ -641,30 +683,40 @@ impl LayerModel {
}
/// Set the [`ScissorBox`] of this layer.
pub fn set_scissor_box(&self, scissor_box:Option<&ScissorBox>) {
pub fn set_scissor_box(&self, scissor_box: Option<&ScissorBox>) {
*self.scissor_box.borrow_mut() = scissor_box.cloned();
}
/// Add depth-order dependency between two [`LayerItem`]s in this layer. Returns `true`
/// if the dependency was inserted successfully (was not already present), and `false`
/// otherwise. All sublayers will inherit these rules.
pub fn add_global_elements_order_dependency
(&self, below:impl Into<LayerItem>, above:impl Into<LayerItem>) -> bool {
pub fn add_global_elements_order_dependency(
&self,
below: impl Into<LayerItem>,
above: impl Into<LayerItem>,
) -> bool {
let below = below.into();
let above = above.into();
let fresh = self.global_element_depth_order.borrow_mut().insert_dependency(below,above);
if fresh { self.sublayers.element_depth_order_dirty.set(); }
let fresh = self.global_element_depth_order.borrow_mut().insert_dependency(below, above);
if fresh {
self.sublayers.element_depth_order_dirty.set();
}
fresh
}
/// Remove a depth-order dependency between two [`LayerItem`]s in this layer. Returns `true`
/// if the dependency was found, and `false` otherwise.
pub fn remove_global_elements_order_dependency
(&self, below:impl Into<LayerItem>, above:impl Into<LayerItem>) -> bool {
pub fn remove_global_elements_order_dependency(
&self,
below: impl Into<LayerItem>,
above: impl Into<LayerItem>,
) -> bool {
let below = below.into();
let above = above.into();
let found = self.global_element_depth_order.borrow_mut().remove_dependency(below,above);
if found { self.sublayers.element_depth_order_dirty.set(); }
let found = self.global_element_depth_order.borrow_mut().remove_dependency(below, above);
if found {
self.sublayers.element_depth_order_dirty.set();
}
found
}
@ -672,32 +724,36 @@ impl LayerModel {
/// This implementation can be simplified to `S1:KnownShapeSystemId` (not using [`Content`] at
/// all), after the compiler gets updated to newer version. Returns `true` if the dependency was
/// inserted successfully (was not already present), and `false` otherwise.
pub fn add_global_shapes_order_dependency<S1,S2>
(&self) -> (bool,PhantomData<S1>,PhantomData<S2>) where
S1 : HasContent,
S2 : HasContent,
Content<S1> : KnownShapeSystemId,
Content<S2> : KnownShapeSystemId {
pub fn add_global_shapes_order_dependency<S1, S2>(
&self,
) -> (bool, PhantomData<S1>, PhantomData<S2>)
where
S1: HasContent,
S2: HasContent,
Content<S1>: KnownShapeSystemId,
Content<S2>: KnownShapeSystemId, {
let s1_id = <Content<S1>>::shape_system_id();
let s2_id = <Content<S2>>::shape_system_id();
let fresh = self.add_global_elements_order_dependency(s1_id,s2_id);
(fresh,default(),default())
let fresh = self.add_global_elements_order_dependency(s1_id, s2_id);
(fresh, default(), default())
}
/// # Future Improvements
/// This implementation can be simplified to `S1:KnownShapeSystemId` (not using [`Content`] at
/// all), after the compiler gets updated to newer version. Returns `true` if the dependency was
/// found, and `false` otherwise.
pub fn remove_global_shapes_order_dependency<S1,S2>
(&self) -> (bool,PhantomData<S1>,PhantomData<S2>) where
S1 : HasContent,
S2 : HasContent,
Content<S1> : KnownShapeSystemId,
Content<S2> : KnownShapeSystemId {
pub fn remove_global_shapes_order_dependency<S1, S2>(
&self,
) -> (bool, PhantomData<S1>, PhantomData<S2>)
where
S1: HasContent,
S2: HasContent,
Content<S1>: KnownShapeSystemId,
Content<S2>: KnownShapeSystemId, {
let s1_id = <Content<S1>>::shape_system_id();
let s2_id = <Content<S2>>::shape_system_id();
let found = self.remove_global_elements_order_dependency(s1_id,s2_id);
(found,default(),default())
let found = self.remove_global_elements_order_dependency(s1_id, s2_id);
(found, default(), default())
}
}
@ -734,16 +790,16 @@ impl std::borrow::Borrow<LayerModel> for Layer {
#[derive(Debug)]
#[allow(missing_docs)]
pub struct LayerDynamicShapeInstance {
pub layer : WeakLayer,
pub symbol_id : SymbolId,
pub instance_id : attribute::InstanceIndex
pub layer: WeakLayer,
pub symbol_id: SymbolId,
pub instance_id: attribute::InstanceIndex,
}
impl LayerDynamicShapeInstance {
/// Constructor.
pub fn new(layer:&Layer, symbol_id:SymbolId, instance_id:attribute::InstanceIndex) -> Self {
pub fn new(layer: &Layer, symbol_id: SymbolId, instance_id: attribute::InstanceIndex) -> Self {
let layer = layer.downgrade();
Self {layer,symbol_id,instance_id}
Self { layer, symbol_id, instance_id }
}
}
@ -754,10 +810,10 @@ impl LayerDynamicShapeInstance {
// =================
/// Abstraction for layer sublayers.
#[derive(Clone,CloneRef,Debug)]
#[derive(Clone, CloneRef, Debug)]
pub struct Sublayers {
model : Rc<RefCell<SublayersModel>>,
element_depth_order_dirty : dirty::SharedBool,
model: Rc<RefCell<SublayersModel>>,
element_depth_order_dirty: dirty::SharedBool,
}
impl Deref for Sublayers {
@ -770,17 +826,17 @@ impl Deref for Sublayers {
impl Eq for Sublayers {}
impl PartialEq for Sublayers {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.model,&other.model)
Rc::ptr_eq(&self.model, &other.model)
}
}
impl Sublayers {
/// Constructor.
pub fn new(logger:impl AnyLogger) -> Self {
let element_dirty_logger = Logger::new_sub(&logger,"dirty");
let model = default();
let element_depth_order_dirty = dirty::SharedBool::new(element_dirty_logger,());
Self {model,element_depth_order_dirty}
pub fn new(logger: impl AnyLogger) -> Self {
let element_dirty_logger = Logger::new_sub(&logger, "dirty");
let model = default();
let element_depth_order_dirty = dirty::SharedBool::new(element_dirty_logger, ());
Self { model, element_depth_order_dirty }
}
}
@ -791,10 +847,10 @@ impl Sublayers {
// ======================
/// Internal representation of [`Group`].
#[derive(Debug,Default)]
#[derive(Debug, Default)]
pub struct SublayersModel {
layers : OptVec<WeakLayer>,
layer_placement : HashMap<LayerId,usize>,
layers: OptVec<WeakLayer>,
layer_placement: HashMap<LayerId, usize>,
}
impl SublayersModel {
@ -806,19 +862,19 @@ impl SublayersModel {
self.layers.iter().filter_map(|t| t.upgrade()).collect()
}
fn layer_ix(&self, layer_id:LayerId) -> Option<usize> {
fn layer_ix(&self, layer_id: LayerId) -> Option<usize> {
self.layer_placement.get(&layer_id).copied()
}
fn remove(&mut self, layer_id:LayerId) {
fn remove(&mut self, layer_id: LayerId) {
if let Some(ix) = self.layer_ix(layer_id) {
self.layers.remove(ix);
}
}
/// Query a [`Layer`] based on its [`LayerId`].
pub fn get(&self, layer_id:LayerId) -> Option<Layer> {
self.layer_ix(layer_id).and_then(|ix| self.layers.safe_index(ix).and_then(|t|t.upgrade()))
pub fn get(&self, layer_id: LayerId) -> Option<Layer> {
self.layer_ix(layer_id).and_then(|ix| self.layers.safe_index(ix).and_then(|t| t.upgrade()))
}
}
@ -842,15 +898,15 @@ newtype_prim! {
/// Abstraction over [`SymbolId`] and [`ShapeSystemId`]. Read docs of [`Group`] to learn about its
/// usage scenarios.
#[derive(Clone,Copy,Debug,PartialEq,PartialOrd,Eq,Hash,Ord)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Hash, Ord)]
#[allow(missing_docs)]
pub enum LayerItem {
Symbol (SymbolId),
ShapeSystem (ShapeSystemId)
Symbol(SymbolId),
ShapeSystem(ShapeSystemId),
}
impl From<ShapeSystemId> for LayerItem {
fn from(t:ShapeSystemId) -> Self {
fn from(t: ShapeSystemId) -> Self {
Self::ShapeSystem(t)
}
}
@ -864,21 +920,21 @@ impl From<ShapeSystemId> for LayerItem {
/// An entry containing [`Any`]-encoded [`ShapeSystem`] and information about symbol instance count
/// of this [`ShapeSystem`].
pub struct ShapeSystemRegistryEntry {
shape_system : Box<dyn Any>,
instance_count : usize,
shape_system: Box<dyn Any>,
instance_count: usize,
}
impl Debug for ShapeSystemRegistryEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.instance_count,f)
Debug::fmt(&self.instance_count, f)
}
}
/// Mutable reference to decoded [`ShapeSystemRegistryEntry`].
#[derive(Debug)]
pub struct ShapeSystemRegistryEntryRefMut<'t,T> {
shape_system : &'t mut T,
instance_count : &'t mut usize,
pub struct ShapeSystemRegistryEntryRefMut<'t, T> {
shape_system: &'t mut T,
instance_count: &'t mut usize,
}
shared! { ShapeSystemRegistry
@ -938,34 +994,36 @@ impl {
impl ShapeSystemRegistryData {
fn get_mut<T>(&mut self) -> Option<ShapeSystemRegistryEntryRefMut<T>>
where T : ShapeSystemInstance {
where T: ShapeSystemInstance {
let id = TypeId::of::<T>();
self.shape_system_map.get_mut(&id).and_then(|t| {
let shape_system = t.shape_system.downcast_mut::<T>();
let shape_system = t.shape_system.downcast_mut::<T>();
let instance_count = &mut t.instance_count;
shape_system.map(move |shape_system|
ShapeSystemRegistryEntryRefMut {shape_system,instance_count}
)
shape_system.map(move |shape_system| ShapeSystemRegistryEntryRefMut {
shape_system,
instance_count,
})
})
}
fn register<T>(&mut self, scene:&Scene) -> ShapeSystemRegistryEntryRefMut<T>
where T : ShapeSystemInstance {
let id = TypeId::of::<T>();
fn register<T>(&mut self, scene: &Scene) -> ShapeSystemRegistryEntryRefMut<T>
where T: ShapeSystemInstance {
let id = TypeId::of::<T>();
let system = <T as ShapeSystemInstance>::new(scene);
let any = Box::new(system);
let entry = ShapeSystemRegistryEntry {shape_system:any, instance_count:0};
let any = Box::new(system);
let entry = ShapeSystemRegistryEntry { shape_system: any, instance_count: 0 };
self.shape_system_map.entry(id).insert(entry);
// The following line is safe, as the object was just registered.
self.get_mut().unwrap()
}
fn with_get_or_register_mut<T,F,Out>
(&mut self, scene:&Scene, f:F) -> Out
where F:FnOnce(ShapeSystemRegistryEntryRefMut<T>)->Out, T:ShapeSystemInstance {
fn with_get_or_register_mut<T, F, Out>(&mut self, scene: &Scene, f: F) -> Out
where
F: FnOnce(ShapeSystemRegistryEntryRefMut<T>) -> Out,
T: ShapeSystemInstance, {
match self.get_mut() {
Some(entry) => f(entry),
None => f(self.register(scene))
None => f(self.register(scene)),
}
}
}
@ -986,17 +1044,17 @@ pub type ShapeSystemSymbolInfo = ShapeSystemInfoTemplate<SymbolId>;
/// the shape system (read docs of [`ShapeSystemRegistry`] to learn more). This struct contains
/// information about the compile time depth ordering relations. See the "Compile Time Shapes
/// Ordering Relations" section in docs of [`Group`] to learn more.
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
pub struct ShapeSystemStaticDepthOrdering {
above : Vec<ShapeSystemId>,
below : Vec<ShapeSystemId>,
above: Vec<ShapeSystemId>,
below: Vec<ShapeSystemId>,
}
/// [`ShapeSystemStaticDepthOrdering`] associated with an id.
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
pub struct ShapeSystemInfoTemplate<T> {
id : T,
ordering : ShapeSystemStaticDepthOrdering,
id: T,
ordering: ShapeSystemStaticDepthOrdering,
}
impl<T> Deref for ShapeSystemInfoTemplate<T> {
@ -1007,8 +1065,8 @@ impl<T> Deref for ShapeSystemInfoTemplate<T> {
}
impl<T> ShapeSystemInfoTemplate<T> {
fn new(id:T, ordering:ShapeSystemStaticDepthOrdering) -> Self {
Self {id,ordering}
fn new(id: T, ordering: ShapeSystemStaticDepthOrdering) -> Self {
Self { id, ordering }
}
}
@ -1058,12 +1116,12 @@ macro_rules! shapes_order_dependencies {
/// A rectangular area used to limit rendering of a [`Layer`]. The area contains information about
/// rendering limits from each side of the image (left, right, top, and bottom).
#[allow(missing_docs)]
#[derive(Debug,Clone,Copy)]
#[derive(Debug, Clone, Copy)]
pub struct ScissorBox {
pub min_x : i32,
pub min_y : i32,
pub max_x : i32,
pub max_y : i32,
pub min_x: i32,
pub min_y: i32,
pub max_x: i32,
pub max_y: i32,
}
impl ScissorBox {
@ -1073,30 +1131,30 @@ impl ScissorBox {
let min_y = 0;
let max_x = i32::MAX;
let max_y = i32::MAX;
Self {min_x,min_y,max_x,max_y}
Self { min_x, min_y, max_x, max_y }
}
/// Constructor.
pub fn new_with_position_and_size(position:Vector2<i32>, size:Vector2<i32>) -> Self {
pub fn new_with_position_and_size(position: Vector2<i32>, size: Vector2<i32>) -> Self {
let min_x = position.x;
let min_y = position.y;
let max_x = min_x + size.x;
let max_y = min_y + size.y;
Self {min_x,min_y,max_x,max_y}
Self { min_x, min_y, max_x, max_y }
}
}
impl ScissorBox {
/// The size of the scissor box.
pub fn size(&self) -> Vector2<i32> {
let width = (self.max_x - self.min_x).max(0);
let width = (self.max_x - self.min_x).max(0);
let height = (self.max_y - self.min_y).max(0);
Vector2(width,height)
Vector2(width, height)
}
/// The position of the scissor box computed from the left bottom corner.
pub fn position(&self) -> Vector2<i32> {
Vector2(self.min_x.max(0),self.min_y.max(0))
Vector2(self.min_x.max(0), self.min_y.max(0))
}
}
@ -1107,16 +1165,16 @@ impl Default for ScissorBox {
}
impl PartialSemigroup<ScissorBox> for ScissorBox {
fn concat_mut(&mut self, other:Self) {
self.min_x = Ord::max(self.min_x,other.min_x);
self.min_y = Ord::max(self.min_y,other.min_y);
self.max_x = Ord::min(self.max_x,other.max_x);
self.max_y = Ord::min(self.max_y,other.max_y);
fn concat_mut(&mut self, other: Self) {
self.min_x = Ord::max(self.min_x, other.min_x);
self.min_y = Ord::max(self.min_y, other.min_y);
self.max_x = Ord::min(self.max_x, other.max_x);
self.max_y = Ord::min(self.max_y, other.max_y);
}
}
impl PartialSemigroup<&ScissorBox> for ScissorBox {
fn concat_mut(&mut self, other:&Self) {
fn concat_mut(&mut self, other: &Self) {
self.concat_mut(*other)
}
}

View File

@ -30,10 +30,10 @@ crate::define_endpoints! {
/// `Events` defines a common FRP api that handles mouse over/out events for multiple
/// sub-shapes. It avoids boilerplate of setting up FRP bindings for every single shape,
/// instead the `Shape` frp endpoints can be used.
#[derive(Clone,CloneRef,Default,Debug)]
#[derive(Clone, CloneRef, Default, Debug)]
#[allow(missing_docs)]
pub struct MouseEvents {
pub frp : Frp,
pub frp: Frp,
}
impl Deref for MouseEvents {
@ -50,7 +50,7 @@ impl MouseEvents {
}
/// Connect the given [`ShapeViewEvents`] to the [`Events`] output.
pub fn add_sub_shape<T:DynamicShape>(&self, sub_shape:&ShapeView<T>) {
pub fn add_sub_shape<T: DynamicShape>(&self, sub_shape: &ShapeView<T>) {
frp::extend! { network
self.frp.source.mouse_over <+ sub_shape.events.mouse_over;
self.frp.source.mouse_out <+ sub_shape.events.mouse_out;

View File

@ -5,11 +5,11 @@ use crate::prelude::*;
use crate::display::shape::*;
/// A path following the given points. All joints and endpoints are rounded.
pub fn path(width:f32, points:&[(f32,f32)]) -> AnyShape {
let points = points.iter().map(|(x,y)| (x.px(),y.px()));
let mut result : AnyShape = EmptyShape().into();
for (start,end) in points.clone().zip(points.skip(1)) {
result = (result + Segment(start,end,width.px())).into();
pub fn path(width: f32, points: &[(f32, f32)]) -> AnyShape {
let points = points.iter().map(|(x, y)| (x.px(), y.px()));
let mut result: AnyShape = EmptyShape().into();
for (start, end) in points.clone().zip(points.skip(1)) {
result = (result + Segment(start, end, width.px())).into();
}
result
}

View File

@ -3,4 +3,4 @@
use crate::data::color;
/// Invisible dummy color to catch hover events.
pub const HOVER_COLOR : color::Rgba = color::Rgba::new(1.0,0.0,0.0,0.000_001);
pub const HOVER_COLOR: color::Rgba = color::Rgba::new(1.0, 0.0, 0.0, 0.000_001);

View File

@ -2,10 +2,10 @@
pub mod def;
pub mod shader;
pub mod system;
pub mod style_watch;
pub mod system;
pub use def::*;
pub use shader::*;
pub use system::*;
pub use style_watch::*;
pub use system::*;

View File

@ -1,8 +1,8 @@
//! This module is the root module for all primitive shapes and shape transform definitions.
pub mod primitive;
pub mod class;
pub mod modifier;
pub mod primitive;
pub mod unit;
pub mod var;

View File

@ -2,13 +2,13 @@
use crate::prelude::*;
use super::unit::*;
use super::modifier::*;
use super::unit::*;
use crate::data::color;
use crate::display::shape::primitive::def::var::Var;
use crate::display::shape::primitive::shader::canvas;
use crate::display::shape::primitive::shader::canvas::Canvas;
use crate::display::shape::primitive::def::var::Var;
use crate::data::color;
@ -21,9 +21,9 @@ pub trait Shape = 'static + canvas::Draw;
/// Generic 2d shape representation. You can convert any specific shape type to this type and use it
/// as a generic shape type.
#[derive(Debug,Clone,CloneRef)]
#[derive(Debug, Clone, CloneRef)]
pub struct AnyShape {
rc: Rc<dyn canvas::Draw>
rc: Rc<dyn canvas::Draw>,
}
impl AsOwned for AnyShape {
@ -32,13 +32,13 @@ impl AsOwned for AnyShape {
impl AnyShape {
/// Constructor.
pub fn new<T:Shape>(t:T) -> Self {
Self {rc : Rc::new(t)}
pub fn new<T: Shape>(t: T) -> Self {
Self { rc: Rc::new(t) }
}
}
impl canvas::Draw for AnyShape {
fn draw(&self, canvas:&mut Canvas) -> canvas::Shape {
fn draw(&self, canvas: &mut Canvas) -> canvas::Shape {
self.rc.draw(canvas)
}
}
@ -50,22 +50,22 @@ impl canvas::Draw for AnyShape {
// ================
/// Immutable reference to a shape. It is also used to get unique id for each shape.
#[derive(Debug,Derivative,Shrinkwrap)]
#[derivative(Clone(bound=""))]
#[derive(Debug, Derivative, Shrinkwrap)]
#[derivative(Clone(bound = ""))]
pub struct ShapeRef<T> {
rc:Rc<T>
rc: Rc<T>,
}
impl<T> From<&ShapeRef<T>> for ShapeRef<T> {
fn from(t:&ShapeRef<T>) -> Self {
fn from(t: &ShapeRef<T>) -> Self {
t.clone()
}
}
impl<T> ShapeRef<T> {
/// Constructor.
pub fn new(t:T) -> Self {
Self {rc:Rc::new(t)}
pub fn new(t: T) -> Self {
Self { rc: Rc::new(t) }
}
/// Unwraps the shape and provides the raw reference to its content.
@ -80,7 +80,7 @@ impl<T> ShapeRef<T> {
/// want to define `s4 = s1 - s2`, `s5 = s1 - s3`, and `s6 = s4 + s5`. We need to discover that
/// we use `s1` twice under the hood in order to optimize the GLSL.
pub fn id(&self) -> usize {
Rc::downgrade(&self.rc).as_ptr() as *const() as usize
Rc::downgrade(&self.rc).as_ptr() as *const () as usize
}
}
@ -91,55 +91,56 @@ impl<T> ShapeRef<T> {
// ================
impl<T> ShapeOps for ShapeRef<T> {}
impl ShapeOps for AnyShape {}
impl ShapeOps for AnyShape {}
/// Methods implemented by every shape.
pub trait ShapeOps : Sized where for<'t> &'t Self : IntoOwned<Owned=Self> {
pub trait ShapeOps: Sized
where for<'t> &'t Self: IntoOwned<Owned = Self> {
/// Translate the shape by a given offset.
fn translate<V:Into<Var<Vector2<Pixels>>>>(&self, v:V) -> Translate<Self> {
Translate(self,v)
fn translate<V: Into<Var<Vector2<Pixels>>>>(&self, v: V) -> Translate<Self> {
Translate(self, v)
}
/// Translate the shape along X-axis by a given offset.
fn translate_x<X>(&self, x:X) -> Translate<Self>
where (X,Var<Pixels>) : Into<Var<Vector2<Pixels>>> {
self.translate((x,0.px()))
fn translate_x<X>(&self, x: X) -> Translate<Self>
where (X, Var<Pixels>): Into<Var<Vector2<Pixels>>> {
self.translate((x, 0.px()))
}
/// Translate the shape along Y-axis by a given offset.
fn translate_y<Y>(&self, y:Y) -> Translate<Self>
where (Var<Pixels>,Y) : Into<Var<Vector2<Pixels>>> {
self.translate((0.px(),y))
fn translate_y<Y>(&self, y: Y) -> Translate<Self>
where (Var<Pixels>, Y): Into<Var<Vector2<Pixels>>> {
self.translate((0.px(), y))
}
/// Rotate the shape by a given angle.
fn rotate<A:Into<Var<Radians>>>(&self, angle:A) -> Rotation<Self> {
Rotation(self,angle)
fn rotate<A: Into<Var<Radians>>>(&self, angle: A) -> Rotation<Self> {
Rotation(self, angle)
}
/// Scales the shape by a given value.
fn scale<S:Into<Var<f32>>>(&self, value:S) -> Scale<Self> {
Scale(self,value)
fn scale<S: Into<Var<f32>>>(&self, value: S) -> Scale<Self> {
Scale(self, value)
}
/// Unify the shape with another one.
fn union<S:IntoOwned>(&self, that:S) -> Union<Self,Owned<S>> {
Union(self,that)
fn union<S: IntoOwned>(&self, that: S) -> Union<Self, Owned<S>> {
Union(self, that)
}
/// Subtracts the argument from this shape.
fn difference<S:IntoOwned>(&self, that:S) -> Difference<Self,Owned<S>> {
Difference(self,that)
fn difference<S: IntoOwned>(&self, that: S) -> Difference<Self, Owned<S>> {
Difference(self, that)
}
/// Computes the intersection of the shapes.
fn intersection<S:IntoOwned>(&self, that:S) -> Intersection<Self,Owned<S>> {
Intersection(self,that)
fn intersection<S: IntoOwned>(&self, that: S) -> Intersection<Self, Owned<S>> {
Intersection(self, that)
}
/// Fill the shape with the provided color.
fn fill<Color:Into<Var<color::Rgba>>>(&self, color:Color) -> Fill<Self> {
Fill(self,color)
fn fill<Color: Into<Var<color::Rgba>>>(&self, color: Color) -> Fill<Self> {
Fill(self, color)
}
/// Makes the borders of the shape crisp. Please note that it removes any form of antialiasing
@ -149,17 +150,17 @@ pub trait ShapeOps : Sized where for<'t> &'t Self : IntoOwned<Owned=Self> {
}
/// Grows the shape by the given amount.
fn grow<T:Into<Var<Pixels>>>(&self, value:T) -> Grow<Self> {
fn grow<T: Into<Var<Pixels>>>(&self, value: T) -> Grow<Self> {
Grow(self, value.into())
}
/// Shrinks the shape by the given amount.
fn shrink<T:Into<Var<Pixels>>>(&self, value:T) -> Shrink<Self> {
fn shrink<T: Into<Var<Pixels>>>(&self, value: T) -> Shrink<Self> {
Shrink(self, value.into())
}
/// Repeats the shape with the given tile size.
fn repeat<T:Into<Var<Vector2<Pixels>>>>(&self, value:T) -> Repeat<Self> {
fn repeat<T: Into<Var<Vector2<Pixels>>>>(&self, value: T) -> Repeat<Self> {
Repeat(self, value)
}
}

Some files were not shown because too many files have changed in this diff Show More