curve improvements, started adding CurveEditor widget

This commit is contained in:
Dmitry
2021-08-01 22:51:30 +03:00
parent 3335282e72
commit d94e8c0fac
6 changed files with 301 additions and 102 deletions
+81 -99
View File
@@ -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;
+76
View File
@@ -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))
}
}
+120
View File
@@ -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
View File
@@ -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,
+13
View File
@@ -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),
}
+7
View File
@@ -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);
}