mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
curve improvements, started adding CurveEditor widget
This commit is contained in:
+81
-99
@@ -1,56 +1,20 @@
|
||||
use crate::{
|
||||
color::Color,
|
||||
math::{cubicf, lerpf},
|
||||
visitor::prelude::*,
|
||||
};
|
||||
use std::cmp::Ordering;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub trait Interpolatable: Sized + Clone + Copy + Default {
|
||||
fn step(&self, other: &Self, t: f32) -> Self {
|
||||
if t.eq(&1.0) {
|
||||
*other
|
||||
} else {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
fn lerp(&self, other: &Self, t: f32) -> Self;
|
||||
|
||||
fn cubic(&self, other: &Self, t: f32, m0: f32, m1: f32) -> Self;
|
||||
}
|
||||
|
||||
impl Interpolatable for f32 {
|
||||
fn lerp(&self, other: &Self, t: f32) -> Self {
|
||||
lerpf(*self, *other, t)
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Cubic_Hermite_spline
|
||||
fn cubic(&self, other: &Self, t: f32, m0: f32, m1: f32) -> Self {
|
||||
cubicf(*self, *other, t, m0, m1)
|
||||
fn stepf(p0: f32, p1: f32, t: f32) -> f32 {
|
||||
if t.eq(&1.0) {
|
||||
p1
|
||||
} else {
|
||||
p0
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpolatable for Color {
|
||||
fn lerp(&self, other: &Self, t: f32) -> Self {
|
||||
let p1 = self.as_frgba();
|
||||
let p2 = other.as_frgba();
|
||||
let k = p1.lerp(&p2, t).scale(255.0);
|
||||
Color::from_rgba(k.x as u8, k.y as u8, k.z as u8, k.w as u8)
|
||||
}
|
||||
|
||||
fn cubic(&self, other: &Self, t: f32, m0: f32, m1: f32) -> Self {
|
||||
let p0 = self.as_frgba();
|
||||
let p1 = other.as_frgba();
|
||||
let r = cubicf(p0.x, p1.x, t, m0, m1) * 255.0;
|
||||
let g = cubicf(p0.y, p1.y, t, m0, m1) * 255.0;
|
||||
let b = cubicf(p0.z, p1.z, t, m0, m1) * 255.0;
|
||||
let a = cubicf(p0.w, p1.w, t, m0, m1) * 255.0;
|
||||
Color::from_rgba(r as u8, g as u8, b as u8, a as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Visit)]
|
||||
pub enum CurvePointKind {
|
||||
#[derive(Visit, Clone, Debug, PartialEq)]
|
||||
pub enum CurveKeyKind {
|
||||
Constant,
|
||||
Linear,
|
||||
Cubic {
|
||||
@@ -59,35 +23,51 @@ pub enum CurvePointKind {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Visit)]
|
||||
pub struct CurvePoint<T: Interpolatable> {
|
||||
location: f32,
|
||||
pub value: T,
|
||||
pub kind: CurvePointKind,
|
||||
impl Default for CurveKeyKind {
|
||||
fn default() -> Self {
|
||||
Self::Constant
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Interpolatable> CurvePoint<T> {
|
||||
pub fn interpolate(&self, other: &Self, t: f32) -> T {
|
||||
match self.kind {
|
||||
CurvePointKind::Constant => self.value.step(&other.value, t),
|
||||
CurvePointKind::Linear => self.value.lerp(&other.value, t),
|
||||
CurvePointKind::Cubic {
|
||||
left_tangent,
|
||||
right_tangent,
|
||||
} => self
|
||||
.value
|
||||
.cubic(&other.value, t, left_tangent, right_tangent),
|
||||
#[derive(Visit, Clone, Default, Debug, PartialEq)]
|
||||
pub struct CurveKey {
|
||||
pub id: Uuid,
|
||||
location: f32,
|
||||
pub value: f32,
|
||||
pub kind: CurveKeyKind,
|
||||
}
|
||||
|
||||
impl CurveKey {
|
||||
pub fn new(location: f32, value: f32, kind: CurveKeyKind) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
location,
|
||||
value,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Visit, Default)]
|
||||
pub struct Curve<T: Interpolatable> {
|
||||
points: Vec<CurvePoint<T>>,
|
||||
impl CurveKey {
|
||||
pub fn interpolate(&self, other: &Self, t: f32) -> f32 {
|
||||
match self.kind {
|
||||
CurveKeyKind::Constant => stepf(self.value, other.value, t),
|
||||
CurveKeyKind::Linear => lerpf(self.value, other.value, t),
|
||||
CurveKeyKind::Cubic {
|
||||
left_tangent,
|
||||
right_tangent,
|
||||
} => cubicf(self.value, other.value, t, left_tangent, right_tangent),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_points<T: Interpolatable>(points: &mut [CurvePoint<T>]) {
|
||||
points.sort_by(|a, b| {
|
||||
#[derive(Visit, Default, Clone, Debug, PartialEq)]
|
||||
pub struct Curve {
|
||||
keys: Vec<CurveKey>,
|
||||
}
|
||||
|
||||
fn sort_keys(keys: &mut [CurveKey]) {
|
||||
keys.sort_by(|a, b| {
|
||||
if a.location < b.location {
|
||||
Ordering::Less
|
||||
} else if a.location > b.location {
|
||||
@@ -98,53 +78,56 @@ fn sort_points<T: Interpolatable>(points: &mut [CurvePoint<T>]) {
|
||||
});
|
||||
}
|
||||
|
||||
impl<T: Interpolatable> From<Vec<CurvePoint<T>>> for Curve<T> {
|
||||
fn from(mut points: Vec<CurvePoint<T>>) -> Self {
|
||||
sort_points(&mut points);
|
||||
Self { points }
|
||||
impl From<Vec<CurveKey>> for Curve {
|
||||
fn from(mut keys: Vec<CurveKey>) -> Self {
|
||||
sort_keys(&mut keys);
|
||||
Self { keys }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Interpolatable> Curve<T> {
|
||||
impl Curve {
|
||||
pub fn clear(&mut self) {
|
||||
self.points.clear()
|
||||
self.keys.clear()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.points.is_empty()
|
||||
self.keys.is_empty()
|
||||
}
|
||||
|
||||
pub fn add_point(&mut self, new_point: CurvePoint<T>) {
|
||||
pub fn keys(&self) -> &[CurveKey] {
|
||||
&self.keys
|
||||
}
|
||||
|
||||
pub fn add_key(&mut self, new_key: CurveKey) {
|
||||
let mut insert_at = 0;
|
||||
for (i, point) in self.points.iter().enumerate() {
|
||||
if new_point.location < point.location {
|
||||
for (i, key) in self.keys.iter().enumerate() {
|
||||
if new_key.location < key.location {
|
||||
insert_at = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.points.insert(insert_at, new_point);
|
||||
self.keys.insert(insert_at, new_key);
|
||||
}
|
||||
|
||||
pub fn move_point(&mut self, point_id: usize, location: f32) {
|
||||
if let Some(point) = self.points.get_mut(point_id) {
|
||||
point.location = location;
|
||||
sort_points(&mut self.points);
|
||||
pub fn move_key(&mut self, key_id: usize, location: f32) {
|
||||
if let Some(key) = self.keys.get_mut(key_id) {
|
||||
key.location = location;
|
||||
sort_keys(&mut self.keys);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value_at(&self, location: f32) -> T {
|
||||
if self.points.is_empty() {
|
||||
// stub - zero
|
||||
pub fn value_at(&self, location: f32) -> f32 {
|
||||
if self.keys.is_empty() {
|
||||
// Stub - zero
|
||||
return Default::default();
|
||||
} else if self.points.len() == 1 {
|
||||
// single point - just return its value
|
||||
return self.points.first().unwrap().value;
|
||||
} else if self.points.len() == 2 {
|
||||
// special case for two points (much faster than generic)
|
||||
let pt_a = self.points.get(0).unwrap();
|
||||
let pt_b = self.points.get(1).unwrap();
|
||||
} else if self.keys.len() == 1 {
|
||||
// Single key - just return its value
|
||||
return self.keys.first().unwrap().value;
|
||||
} else if self.keys.len() == 2 {
|
||||
// Special case for two keys (much faster than generic)
|
||||
let pt_a = self.keys.get(0).unwrap();
|
||||
let pt_b = self.keys.get(1).unwrap();
|
||||
if location >= pt_a.location && location <= pt_b.location {
|
||||
// linear interpolation
|
||||
let span = pt_b.location - pt_a.location;
|
||||
let t = (location - pt_a.location) / span;
|
||||
return pt_a.interpolate(pt_b, t);
|
||||
@@ -155,26 +138,25 @@ impl<T: Interpolatable> Curve<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// generic case
|
||||
// check for out-of-bounds
|
||||
let first = self.points.first().unwrap();
|
||||
let last = self.points.last().unwrap();
|
||||
// Generic case - check for out-of-bounds
|
||||
let first = self.keys.first().unwrap();
|
||||
let last = self.keys.last().unwrap();
|
||||
if location <= first.location {
|
||||
first.value
|
||||
} else if location >= last.location {
|
||||
last.value
|
||||
} else {
|
||||
// find span first
|
||||
// Find span first
|
||||
let mut pt_a_index = 0;
|
||||
for (i, pt) in self.points.iter().enumerate() {
|
||||
for (i, pt) in self.keys.iter().enumerate() {
|
||||
if location >= pt.location {
|
||||
pt_a_index = i;
|
||||
}
|
||||
}
|
||||
let pt_b_index = pt_a_index + 1;
|
||||
|
||||
let pt_a = self.points.get(pt_a_index).unwrap();
|
||||
let pt_b = self.points.get(pt_b_index).unwrap();
|
||||
let pt_a = self.keys.get(pt_a_index).unwrap();
|
||||
let pt_b = self.keys.get(pt_b_index).unwrap();
|
||||
|
||||
let span = pt_b.location - pt_a.location;
|
||||
let t = (location - pt_a.location) / span;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
use crate::{
|
||||
core::{pool::Handle, uuid::Uuid},
|
||||
draw::{CommandTexture, Draw, DrawingContext},
|
||||
message::{CurveKeyMessage, MessageData, MessageDirection, UiMessage, UiMessageData},
|
||||
widget::{Widget, WidgetBuilder},
|
||||
BuildContext, Control, UINode, UserInterface,
|
||||
};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Kind {
|
||||
Constant,
|
||||
Linear,
|
||||
Cubic {
|
||||
left_tangent: f32,
|
||||
right_tangent: f32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CurveKey<M: MessageData, C: Control<M, C>> {
|
||||
widget: Widget<M, C>,
|
||||
kind: Kind,
|
||||
id: Uuid,
|
||||
}
|
||||
|
||||
crate::define_widget_deref!(CurveKey<M, C>);
|
||||
|
||||
impl<M: MessageData, C: Control<M, C>> Control<M, C> for CurveKey<M, C> {
|
||||
fn draw(&self, drawing_context: &mut DrawingContext) {
|
||||
let screen_bounds = self.screen_bounds();
|
||||
drawing_context.push_rect_filled(&screen_bounds, None);
|
||||
drawing_context.commit(screen_bounds, self.foreground(), CommandTexture::None, None);
|
||||
}
|
||||
|
||||
fn handle_routed_message(
|
||||
&mut self,
|
||||
ui: &mut UserInterface<M, C>,
|
||||
message: &mut UiMessage<M, C>,
|
||||
) {
|
||||
self.widget.handle_routed_message(ui, message);
|
||||
|
||||
if message.direction() == MessageDirection::ToWidget && message.destination() == self.handle
|
||||
{
|
||||
if let UiMessageData::CurveKey(CurveKeyMessage::Sync(key)) = message.data() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CurveKeyBuilder<M: MessageData, C: Control<M, C>> {
|
||||
widget_builder: WidgetBuilder<M, C>,
|
||||
id: Uuid,
|
||||
}
|
||||
|
||||
impl<M: MessageData, C: Control<M, C>> CurveKeyBuilder<M, C> {
|
||||
pub fn new(widget_builder: WidgetBuilder<M, C>) -> Self {
|
||||
Self {
|
||||
widget_builder,
|
||||
id: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: Uuid) -> Self {
|
||||
self.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self, ui: &mut BuildContext<M, C>) -> Handle<UINode<M, C>> {
|
||||
let key = CurveKey {
|
||||
widget: self.widget_builder.build(),
|
||||
kind: Kind::Linear,
|
||||
id: self.id,
|
||||
};
|
||||
ui.add_node(UINode::CurveKey(key))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
use crate::canvas::CanvasBuilder;
|
||||
use crate::{
|
||||
core::{
|
||||
curve::{Curve, CurveKey},
|
||||
pool::Handle,
|
||||
uuid::Uuid,
|
||||
},
|
||||
curve::key::CurveKeyBuilder,
|
||||
draw::DrawingContext,
|
||||
message::{
|
||||
CurveEditorMessage, MessageData, MessageDirection, UiMessage, UiMessageData, WidgetMessage,
|
||||
},
|
||||
widget::{Widget, WidgetBuilder},
|
||||
BuildContext, Control, UINode, UserInterface,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
pub mod key;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CurveEditor<M: MessageData, C: Control<M, C>> {
|
||||
widget: Widget<M, C>,
|
||||
canvas: Handle<UINode<M, C>>,
|
||||
keys: HashMap<Uuid, Handle<UINode<M, C>>>,
|
||||
}
|
||||
|
||||
crate::define_widget_deref!(CurveEditor<M, C>);
|
||||
|
||||
fn make_key_view<M: MessageData, C: Control<M, C>>(
|
||||
key: &CurveKey,
|
||||
ctx: &mut BuildContext<M, C>,
|
||||
) -> Handle<UINode<M, C>> {
|
||||
CurveKeyBuilder::new(WidgetBuilder::new())
|
||||
.with_id(key.id)
|
||||
.build(ctx)
|
||||
}
|
||||
|
||||
impl<M: MessageData, C: Control<M, C>> Control<M, C> for CurveEditor<M, C> {
|
||||
fn draw(&self, _drawing_context: &mut DrawingContext) {}
|
||||
|
||||
fn handle_routed_message(
|
||||
&mut self,
|
||||
ui: &mut UserInterface<M, C>,
|
||||
message: &mut UiMessage<M, C>,
|
||||
) {
|
||||
self.widget.handle_routed_message(ui, message);
|
||||
|
||||
if message.direction() == MessageDirection::ToWidget && message.destination() == self.handle
|
||||
{
|
||||
if let UiMessageData::CurveEditor(CurveEditorMessage::Sync(curve)) = message.data() {
|
||||
let self_keys = self.keys.clone();
|
||||
if curve.keys().len() < self_keys.len() {
|
||||
// A key was deleted.
|
||||
for (key_id, key_handle) in self_keys.iter() {
|
||||
if curve.keys().iter().all(|k| &k.id != key_id) {
|
||||
ui.send_message(WidgetMessage::remove(
|
||||
*key_handle,
|
||||
MessageDirection::ToWidget,
|
||||
));
|
||||
|
||||
self.keys.remove(key_id);
|
||||
}
|
||||
}
|
||||
} else if curve.keys().len() > self.keys.len() {
|
||||
// A key was added.
|
||||
for key in curve.keys() {
|
||||
if !self.keys.contains_key(&key.id) {
|
||||
let key_view = make_key_view(key, &mut ui.build_ctx());
|
||||
self.keys.insert(key.id, key_view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sync values.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CurveEditorBuilder<M: MessageData, C: Control<M, C>> {
|
||||
widget_builder: WidgetBuilder<M, C>,
|
||||
curve: Curve,
|
||||
}
|
||||
|
||||
impl<M: MessageData, C: Control<M, C>> CurveEditorBuilder<M, C> {
|
||||
pub fn new(widget_builder: WidgetBuilder<M, C>) -> Self {
|
||||
Self {
|
||||
widget_builder,
|
||||
curve: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_curve(mut self, curve: Curve) -> Self {
|
||||
self.curve = curve;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self, ctx: &mut BuildContext<M, C>) -> Handle<UINode<M, C>> {
|
||||
let keys = self
|
||||
.curve
|
||||
.keys()
|
||||
.iter()
|
||||
.map(|k| (k.id, make_key_view(k, ctx)))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let canvas =
|
||||
CanvasBuilder::new(WidgetBuilder::new().with_children(keys.iter().map(|(_, h)| h)))
|
||||
.build(ctx);
|
||||
|
||||
let editor = CurveEditor {
|
||||
widget: self.widget_builder.with_child(canvas).build(),
|
||||
keys,
|
||||
canvas,
|
||||
};
|
||||
ctx.add_node(UINode::CurveEditor(editor))
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -16,8 +16,6 @@ extern crate fontdue;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
extern crate sysinfo;
|
||||
|
||||
use message::PopupMessage;
|
||||
use popup::Placement;
|
||||
pub use rg3d_core as core;
|
||||
|
||||
pub mod border;
|
||||
@@ -26,6 +24,7 @@ pub mod button;
|
||||
pub mod canvas;
|
||||
pub mod check_box;
|
||||
pub mod color;
|
||||
pub mod curve;
|
||||
pub mod decorator;
|
||||
pub mod dock;
|
||||
pub mod draw;
|
||||
@@ -73,12 +72,14 @@ use crate::{
|
||||
draw::{CommandTexture, Draw, DrawingContext},
|
||||
message::{
|
||||
ButtonState, CursorIcon, KeyboardModifiers, MessageData, MessageDirection, MouseButton,
|
||||
OsEvent, UiMessage, UiMessageData, WidgetMessage,
|
||||
OsEvent, PopupMessage, UiMessage, UiMessageData, WidgetMessage,
|
||||
},
|
||||
node::UINode,
|
||||
popup::Placement,
|
||||
ttf::{Font, SharedFont},
|
||||
widget::{Widget, WidgetBuilder},
|
||||
};
|
||||
|
||||
use copypasta::ClipboardContext;
|
||||
use std::{
|
||||
cell::Cell,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
//! internal library code can send such messages without a risk of breaking anything.
|
||||
|
||||
use crate::core::algebra::{Vector2, Vector3};
|
||||
use crate::core::curve::{Curve, CurveKey};
|
||||
use crate::dock::SplitDirection;
|
||||
use crate::file_browser::Filter;
|
||||
use crate::formatted_text::WrapMode;
|
||||
@@ -899,6 +900,16 @@ impl ColorFieldMessage {
|
||||
define_constructor_unbound!(ColorField(ColorFieldMessage:Color) => fn color(Color), layout: false);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum CurveEditorMessage {
|
||||
Sync(Curve),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum CurveKeyMessage {
|
||||
Sync(CurveKey),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum UiMessageData<M: MessageData, C: Control<M, C>> {
|
||||
Widget(WidgetMessage<M, C>),
|
||||
@@ -932,6 +943,8 @@ pub enum UiMessageData<M: MessageData, C: Control<M, C>> {
|
||||
ColorField(ColorFieldMessage),
|
||||
Expander(ExpanderMessage),
|
||||
SaturationBrightnessField(SaturationBrightnessFieldMessage),
|
||||
CurveEditor(CurveEditorMessage),
|
||||
CurveKey(CurveKeyMessage),
|
||||
User(M),
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::{
|
||||
check_box::CheckBox,
|
||||
color::{AlphaBar, ColorField, ColorPicker, HueBar, SaturationBrightnessField},
|
||||
core::{algebra::Vector2, define_is_as, pool::Handle},
|
||||
curve::{key::CurveKey, CurveEditor},
|
||||
decorator::Decorator,
|
||||
dock::{DockingManager, Tile},
|
||||
draw::DrawingContext,
|
||||
@@ -78,6 +79,8 @@ pub enum UINode<M: MessageData, C: Control<M, C>> {
|
||||
WrapPanel(WrapPanel<M, C>),
|
||||
VectorImage(VectorImage<M, C>),
|
||||
Expander(Expander<M, C>),
|
||||
CurveEditor(CurveEditor<M, C>),
|
||||
CurveKey(CurveKey<M, C>),
|
||||
User(C),
|
||||
}
|
||||
|
||||
@@ -123,6 +126,8 @@ macro_rules! static_dispatch {
|
||||
UINode::MessageBox(v) => v.$func($($args),*),
|
||||
UINode::VectorImage(v) => v.$func($($args),*),
|
||||
UINode::Expander(v) => v.$func($($args),*),
|
||||
UINode::CurveEditor(v) => v.$func($($args),*),
|
||||
UINode::CurveKey(v) => v.$func($($args),*),
|
||||
UINode::User(v) => v.$func($($args),*),
|
||||
}
|
||||
};
|
||||
@@ -180,6 +185,8 @@ impl<M: MessageData, C: Control<M, C>> UINode<M, C> {
|
||||
define_is_as!(UINode : WrapPanel -> ref WrapPanel<M, C> => fn is_wrap_panel, fn as_wrap_panel, fn as_wrap_panel_mut);
|
||||
define_is_as!(UINode : VectorImage -> ref VectorImage<M, C> => fn is_vector_image, fn as_vector_image, fn as_vector_image_mut);
|
||||
define_is_as!(UINode : Expander -> ref Expander<M, C> => fn is_expander, fn as_expander, fn as_expander_mut);
|
||||
define_is_as!(UINode : CurveEditor -> ref CurveEditor<M, C> => fn is_curve_editor, fn as_curve_editor, fn as_curve_editor_mut);
|
||||
define_is_as!(UINode : CurveKey -> ref CurveKey<M, C> => fn is_curve_key, fn as_curve_key, fn as_curve_key_mut);
|
||||
define_is_as!(UINode : User -> ref C => fn is_user, fn as_user, fn as_user_mut);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user