mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 21:41:34 +03:00
Update code style to use rust fmt (#3131)
This commit is contained in:
parent
ab0f50a7a3
commit
c822256e6c
2
.github/workflows/gui.yml
vendored
2
.github/workflows/gui.yml
vendored
@ -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
|
||||
|
@ -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'])
|
||||
}
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
74
gui/rustfmt.toml
Normal 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
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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::*;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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![]));
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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")),
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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::*;
|
||||
|
@ -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);
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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),*>)?; )*) => {$(
|
||||
|
@ -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);
|
||||
|
@ -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
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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::*;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 };
|
||||
|
@ -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
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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::*;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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::*;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user