From a415edb6aee6717ddda4aeb77ae14a9afe163899 Mon Sep 17 00:00:00 2001 From: Konstantin Dmitriev Date: Dec 13 2019 04:34:01 +0000 Subject: Merge PR #982: Improves Graphs widget/panel (related to #267) --- diff --git a/synfig-core/src/synfig/timepointcollect.cpp b/synfig-core/src/synfig/timepointcollect.cpp index d02d7b1..db84b45 100644 --- a/synfig-core/src/synfig/timepointcollect.cpp +++ b/synfig-core/src/synfig/timepointcollect.cpp @@ -139,6 +139,76 @@ synfig::waypoint_collect(set > &waypoint_set, return 0; } +bool +synfig::waypoint_search(Waypoint& waypoint, const UniqueID &uid, const etl::handle &node) +{ + // Check if we are a linkable value node + LinkableValueNode::Handle linkable_value_node; + linkable_value_node=linkable_value_node.cast_dynamic(node); + if(linkable_value_node) + { + const int link_count(linkable_value_node->link_count()); + for(int i=0; i < link_count; i++) { + bool ret = waypoint_search(waypoint, uid, linkable_value_node->get_link(i).get()); + if (ret) + return true; + } + return false; + } + + // Check if we are a layer + Layer::Handle layer; + layer=layer.cast_dynamic(node); + if(layer) + { + const Layer::DynamicParamList& dyn_param_list(layer->dynamic_param_list()); + + for(Layer::DynamicParamList::const_iterator iter=dyn_param_list.begin(); iter!=dyn_param_list.end(); ++iter) { + bool ret = waypoint_search(waypoint, uid, iter->second); + if (ret) + return true; + } + + ValueBase canvas_value(layer->get_param("canvas")); + if(canvas_value.get_type()==type_canvas) + { + bool ret = waypoint_search(waypoint, uid, + Canvas::Handle(canvas_value.get(Canvas::Handle()))); + if (ret) + return true; + } + return false; + } + + // Check if we are a canvas + Canvas::Handle canvas; + canvas=canvas.cast_dynamic(node); + if(canvas) + { + for(Canvas::const_iterator iter = canvas->begin(); iter!=canvas->end(); ++iter) { + bool ret = waypoint_search(waypoint, uid, *iter); + if (ret) + return true; + } + return false; + } + + // Check if we are an animated value node + ValueNode_Animated::Handle value_node_animated; + value_node_animated=value_node_animated.cast_dynamic(node); + if(value_node_animated) + { + ValueNode_AnimatedInterfaceConst::const_findresult result = value_node_animated->find_uid(uid); + if (!result.second) + return false; + + waypoint = *result.first; + return true; + } + + return false; +} + //! \writeme int synfig::activepoint_collect(set >& /*activepoint_set*/,const Time& time, const etl::handle& node) diff --git a/synfig-core/src/synfig/timepointcollect.h b/synfig-core/src/synfig/timepointcollect.h index 7f6827c..647c413 100644 --- a/synfig-core/src/synfig/timepointcollect.h +++ b/synfig-core/src/synfig/timepointcollect.h @@ -44,6 +44,9 @@ namespace synfig { //! \writeme int waypoint_collect(std::set >& waypoint_set,const Time& time, const etl::handle& node); +//! Search for a specific waypoint (by its uid) in node. +bool waypoint_search(Waypoint& waypoint, const UniqueID& uid, const etl::handle& node); + //! \writeme int activepoint_collect(std::set >& activepoint_set,const Time& time, const etl::handle& node); diff --git a/synfig-core/src/synfig/valuenodes/valuenode_animatedinterface.cpp b/synfig-core/src/synfig/valuenodes/valuenode_animatedinterface.cpp index 4898336..f6b5613 100644 --- a/synfig-core/src/synfig/valuenodes/valuenode_animatedinterface.cpp +++ b/synfig-core/src/synfig/valuenodes/valuenode_animatedinterface.cpp @@ -1111,8 +1111,6 @@ ValueNode_AnimatedInterfaceConst::waypoint_is_only_use_of_valuenode(Waypoint &wa void ValueNode_AnimatedInterfaceConst::erase(const UniqueID &x) { - // \todo printf? - printf("%s:%d erasing waypoint from %lx\n", __FILE__, __LINE__, uintptr_t(this)); WaypointList::iterator iter(find(x)); Waypoint waypoint(*iter); assert(waypoint.get_value_node()); diff --git a/synfig-studio/src/gui/CMakeLists.txt b/synfig-studio/src/gui/CMakeLists.txt index e011e90..0ba29ba 100644 --- a/synfig-studio/src/gui/CMakeLists.txt +++ b/synfig-studio/src/gui/CMakeLists.txt @@ -49,7 +49,9 @@ target_sources(synfigstudio "${CMAKE_CURRENT_LIST_DIR}/resourcehelper.cpp" "${CMAKE_CURRENT_LIST_DIR}/splash.cpp" "${CMAKE_CURRENT_LIST_DIR}/statemanager.cpp" + "${CMAKE_CURRENT_LIST_DIR}/timeplotdata.cpp" "${CMAKE_CURRENT_LIST_DIR}/valuelink.cpp" + "${CMAKE_CURRENT_LIST_DIR}/waypointrenderer.cpp" "${CMAKE_CURRENT_LIST_DIR}/workarea.cpp" "${CMAKE_CURRENT_LIST_DIR}/workspacehandler.cpp" "${CMAKE_CURRENT_LIST_DIR}/main_win32.cpp" diff --git a/synfig-studio/src/gui/Makefile.am b/synfig-studio/src/gui/Makefile.am index 88564fa..989fd71 100644 --- a/synfig-studio/src/gui/Makefile.am +++ b/synfig-studio/src/gui/Makefile.am @@ -56,9 +56,12 @@ OTHER_HH = \ renddesc.h \ render.h \ resourcehelper.h \ + selectdraghelper.h \ splash.h \ statemanager.h \ + timeplotdata.h \ valuelink.h \ + waypointrenderer.h \ workarea.h \ workspacehandler.h \ main_win32.h \ @@ -86,7 +89,9 @@ OTHER_CC = \ resourcehelper.cpp \ splash.cpp \ statemanager.cpp \ + timeplotdata.cpp \ valuelink.cpp \ + waypointrenderer.cpp \ workarea.cpp \ workspacehandler.cpp \ main_win32.cpp \ diff --git a/synfig-studio/src/gui/cellrenderer/cellrenderer_timetrack.cpp b/synfig-studio/src/gui/cellrenderer/cellrenderer_timetrack.cpp index 39deb7c..b4dc5a1 100644 --- a/synfig-studio/src/gui/cellrenderer/cellrenderer_timetrack.cpp +++ b/synfig-studio/src/gui/cellrenderer/cellrenderer_timetrack.cpp @@ -47,6 +47,8 @@ #include +#include "gui/waypointrenderer.h" + #endif using namespace synfig; @@ -69,233 +71,6 @@ namespace { /* === P R O C E D U R E S ================================================= */ -static Gdk::RGBA -get_interp_color(Interpolation x) -{ - switch(x) { - case INTERPOLATION_TCB: - return Gdk::RGBA("#73d216"); - case INTERPOLATION_LINEAR: - return Gdk::RGBA("#edd400"); - case INTERPOLATION_CONSTANT: - return Gdk::RGBA("#cc0000"); - case INTERPOLATION_HALT: - return Gdk::RGBA("#3465a4"); - case INTERPOLATION_MANUAL: - return Gdk::RGBA("#75507b"); - case INTERPOLATION_CLAMPED: - return Gdk::RGBA("#c17d11"); - case INTERPOLATION_UNDEFINED: - default: - break; - } - return Gdk::RGBA("#555753"); -} - -static Gdk::RGBA -color_darken(Gdk::RGBA x, float amount) -{ - x.set_red(x.get_red() * amount); - x.set_green(x.get_green() * amount); - x.set_blue(x.get_blue() * amount); - return x; -} - -static void -render_time_point_to_window( - const Cairo::RefPtr &cr, - const Gdk::Rectangle& area, - const TimePoint &tp, - bool selected ) -{ - const Gdk::RGBA black("#2e3436"); // it's black, trust me - - if(selected) - cr->set_line_width(2.0); - else - cr->set_line_width(1.0); - - Gdk::RGBA color; - -/*- BEFORE ------------------------------------- */ - - color=get_interp_color(tp.get_before()); - color=color_darken(color,1.0f); - if(selected)color=color_darken(color,1.3f); - cr->set_source_rgb(color.get_red(),color.get_green(),color.get_blue()); - - switch(tp.get_before()) - { - case INTERPOLATION_TCB: - cr->save(); - cr->translate(area.get_x(), area.get_y()); - cr->scale(area.get_width(), area.get_height()); - cr->arc(0.5, 0.5, 0.5, 90*M_PI/180.0, 270*M_PI/180.0); - cr->fill_preserve(); - cr->restore(); - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->stroke(); - break; - - case INTERPOLATION_HALT: - cr->save(); - cr->translate(area.get_x(), area.get_y()); - cr->scale(area.get_width(), area.get_height()*2); - cr->move_to(0.5, 0.5); - cr->arc(0.5, 0.5, 0.5, 180*M_PI/180.0, 270*M_PI/180.0); - cr->fill(); - cr->arc(0.5, 0.5, 0.5, 180*M_PI/180.0, 270*M_PI/180.0); - cr->restore(); - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->stroke(); - - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->move_to(area.get_x(),area.get_y()+area.get_height()); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); - cr->stroke(); - break; - - case INTERPOLATION_LINEAR: - cr->save(); - cr->move_to(area.get_x()+area.get_width()/2,area.get_y()); - cr->line_to(area.get_x(),area.get_y()+area.get_height()); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); - cr->fill_preserve(); - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->stroke(); - cr->restore(); - break; - - case INTERPOLATION_CONSTANT: - cr->save(); - cr->move_to(area.get_x()+area.get_width()/2,area.get_y()); - cr->line_to(area.get_x()+area.get_width()/4,area.get_y()); - cr->line_to(area.get_x()+area.get_width()/4,area.get_y()+area.get_height()/2); - cr->line_to(area.get_x(),area.get_y()+area.get_height()/2); - cr->line_to(area.get_x(),area.get_y()+area.get_height()); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); - cr->fill_preserve(); - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->stroke(); - cr->restore(); - break; - - case INTERPOLATION_CLAMPED: - cr->save(); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()); - cr->line_to(area.get_x(),area.get_y()+area.get_height()/2); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); - cr->fill_preserve(); - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->stroke(); - cr->restore(); - break; - - default: - cr->save(); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()); - cr->line_to(area.get_x()+area.get_width()/3,area.get_y()); - cr->line_to(area.get_x(),area.get_y()+area.get_height()/3); - cr->line_to(area.get_x(),area.get_y()+area.get_height()-area.get_height()/3); - cr->line_to(area.get_x()+area.get_width()/3,area.get_y()+area.get_height()); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); - cr->fill_preserve(); - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->stroke(); - cr->restore(); - break; - } - -/*- AFTER -------------------------------------- */ - - color=get_interp_color(tp.get_after()); - color=color_darken(color,0.8f); - if(selected)color=color_darken(color,1.3f); - cr->set_source_rgb(color.get_red(),color.get_green(),color.get_blue()); - - switch(tp.get_after()) - { - case INTERPOLATION_TCB: - cr->save(); - cr->translate(area.get_x(), area.get_y()); - cr->scale(area.get_width(), area.get_height()); - cr->arc(0.5, 0.5, 0.5, -90*M_PI/180.0, 90*M_PI/180.0); - cr->fill_preserve(); - cr->restore(); - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->stroke(); - break; - - case INTERPOLATION_HALT: - cr->save(); - cr->translate(area.get_x(), area.get_y()); - cr->scale(area.get_width(), area.get_height()*2); - cr->move_to(0.5, 0.0); - cr->arc(0.5, 0.0, 0.5, 0*M_PI/180.0, 90*M_PI/180.0); - cr->fill(); - cr->arc(0.5, 0.0, 0.5, 0*M_PI / 180.0, 90*M_PI / 180.0); - cr->restore(); - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->stroke(); - - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->move_to(area.get_x()+area.get_width()/2,area.get_y()); - cr->line_to(area.get_x()+area.get_width(),area.get_y()); - cr->stroke(); - break; - - case INTERPOLATION_LINEAR: - cr->save(); - cr->move_to(area.get_x()+area.get_width()/2,area.get_y()); - cr->line_to(area.get_x()+area.get_width(),area.get_y()); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); - cr->fill_preserve(); - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->stroke(); - cr->restore(); - break; - - case INTERPOLATION_CONSTANT: - cr->save(); - cr->move_to(area.get_x()+area.get_width()/2,area.get_y()); - cr->line_to(area.get_x()+area.get_width(),area.get_y()); - cr->line_to(area.get_x()+area.get_width(),area.get_y()+area.get_height()/2); - cr->line_to(area.get_x()+area.get_width()-area.get_width()/4,area.get_y()+area.get_height()/2); - cr->line_to(area.get_x()+area.get_width()-area.get_width()/4,area.get_y()+area.get_height()); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); - cr->fill_preserve(); - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->stroke(); - cr->restore(); - break; - - case INTERPOLATION_CLAMPED: - cr->save(); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()); - cr->line_to(area.get_x()+area.get_width(),area.get_y()+area.get_height()/2); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); - cr->fill_preserve(); - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->stroke(); - cr->restore(); - break; - - default: - cr->save(); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()); - cr->line_to(area.get_x()+area.get_width()-area.get_width()/3,area.get_y()); - cr->line_to(area.get_x()+area.get_width(),area.get_y()+area.get_height()/3); - cr->line_to(area.get_x()+area.get_width(),area.get_y()+area.get_height()-area.get_height()/3); - cr->line_to(area.get_x()+area.get_width()-area.get_width()/3,area.get_y()+area.get_height()); - cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); - cr->fill_preserve(); - cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); - cr->stroke(); - cr->restore(); - break; - } -} - static void draw_activepoint_off( const Cairo::RefPtr &cr, @@ -611,7 +386,7 @@ CellRenderer_TimeTrack::render_vfunc( cell_area.get_height() - 2 ); TimePoint tp_copy = *i; tp_copy.set_time(t); - render_time_point_to_window(cr, area, tp_copy, selected); + WaypointRenderer::render_time_point_to_window(cr, area, tp_copy, selected, false); } } @@ -622,7 +397,7 @@ CellRenderer_TimeTrack::render_vfunc( cell_area.get_y() + 1, cell_area.get_height() - 2, cell_area.get_height() - 2 ); - render_time_point_to_window(cr, area, *i, true); + WaypointRenderer::render_time_point_to_window(cr, area, *i, true, false); } } diff --git a/synfig-studio/src/gui/dialogs/dialog_waypoint.cpp b/synfig-studio/src/gui/dialogs/dialog_waypoint.cpp index 26fbbf8..17e6c8f 100644 --- a/synfig-studio/src/gui/dialogs/dialog_waypoint.cpp +++ b/synfig-studio/src/gui/dialogs/dialog_waypoint.cpp @@ -43,6 +43,8 @@ #include "widgets/widget_time.h" #include "widgets/widget_waypoint.h" +#include + #include #endif @@ -113,7 +115,7 @@ void Dialog_Waypoint::on_delete_pressed() { hide(); - signal_delete_(); + signal_delete_(); } void @@ -134,9 +136,32 @@ Dialog_Waypoint::set_value_desc(synfigapp::ValueDesc value_desc) value_desc_=value_desc; if(value_desc.get_value_node() && value_desc.get_value_node()->get_parent_canvas()) waypointwidget->set_canvas(value_desc.get_value_node()->get_parent_canvas()); + + if (value_desc.is_value_node()) + value_desc_changed = value_desc.get_value_node()->signal_changed().connect( + sigc::mem_fun(*this, &Dialog_Waypoint::refresh )); + if (value_desc.parent_is_value_node()) + value_desc_changed = value_desc.get_parent_value_node()->signal_changed().connect( + sigc::mem_fun(*this, &Dialog_Waypoint::refresh )); + if (value_desc.parent_is_layer()) + value_desc_changed = value_desc.get_layer()->signal_changed().connect( + sigc::mem_fun(*this, &Dialog_Waypoint::refresh )); + } void Dialog_Waypoint::reset() { } + +void +Dialog_Waypoint::refresh() +{ + Waypoint refreshed_waypoint; + bool ok = synfig::waypoint_search(refreshed_waypoint, waypointwidget->get_waypoint(), value_desc_.get_value_node()); + if (!ok) + hide(); + else + set_waypoint(refreshed_waypoint); +} + diff --git a/synfig-studio/src/gui/dialogs/dialog_waypoint.h b/synfig-studio/src/gui/dialogs/dialog_waypoint.h index c0aaeb1..2ae0f4e 100644 --- a/synfig-studio/src/gui/dialogs/dialog_waypoint.h +++ b/synfig-studio/src/gui/dialogs/dialog_waypoint.h @@ -71,7 +71,6 @@ class Dialog_Waypoint : public Gtk::Dialog { Widget_Waypoint *waypointwidget; etl::handle canvas; - synfig::ValueNode_Animated::WaypointList::iterator waypoint; synfigapp::ValueDesc value_desc_; sigc::signal signal_changed_; @@ -81,6 +80,10 @@ class Dialog_Waypoint : public Gtk::Dialog void on_apply_pressed(); void on_delete_pressed(); + sigc::connection value_desc_changed; + + void refresh(); + public: Dialog_Waypoint(Gtk::Window& parent,etl::handle canvas); ~Dialog_Waypoint(); diff --git a/synfig-studio/src/gui/docks/dock_curves.cpp b/synfig-studio/src/gui/docks/dock_curves.cpp index 17c382d..58334f2 100644 --- a/synfig-studio/src/gui/docks/dock_curves.cpp +++ b/synfig-studio/src/gui/docks/dock_curves.cpp @@ -74,9 +74,9 @@ Dock_Curves::~Dock_Curves() } static void -_curve_selection_changed(Gtk::TreeView* param_tree_view,Widget_Curves* curves) +_curve_selection_changed(Gtk::TreeView* param_tree_view, Widget_Curves* curves, Dock_Curves* dock) { - LayerParamTreeStore::Model model; + LayerParamTreeStore::Model param_model; Gtk::TreeIter iter; if(!param_tree_view->get_selection()->count_selected_rows()) { @@ -85,11 +85,27 @@ _curve_selection_changed(Gtk::TreeView* param_tree_view,Widget_Curves* curves) return; } - std::list value_descs; - - iter=param_tree_view->get_selection()->get_selected(); - value_descs.push_back((*iter)[model.value_desc]); - curves->set_value_descs(value_descs); + std::list< std::pair > value_descs; + + auto path_list = param_tree_view->get_selection()->get_selected_rows(); + auto model = param_tree_view->get_model(); + for (auto path_it : path_list) { + auto iter = model->get_iter(path_it); + std::string name; + + { + auto iter2 = iter; + while (iter2) { + std::string current_label = (*iter2)[param_model.label].operator Glib::ustring(); + name = current_label + ":" + name; + iter2 = iter2->parent(); + } + name.pop_back(); + } + + value_descs.push_back( std::pair (name, (*iter)[param_model.value_desc])); + } + curves->set_value_descs(dock->get_canvas_interface(), value_descs); } void @@ -109,8 +125,10 @@ Dock_Curves::init_canvas_view_vfunc(etl::loose_handle canvas_view) param_tree_view->get_selection()->signal_changed().connect( sigc::bind( sigc::bind( - sigc::ptr_fun( - _curve_selection_changed + sigc::bind( + sigc::ptr_fun( + _curve_selection_changed + ),this ),curves ),param_tree_view ) @@ -120,6 +138,20 @@ Dock_Curves::init_canvas_view_vfunc(etl::loose_handle canvas_view) tree_layer->signal_param_tree_header_height_changed().connect( sigc::mem_fun(*this, &studio::Dock_Curves::on_update_header_height) ); + curves->signal_waypoint_clicked().connect([=](synfigapp::ValueDesc value_desc, std::set> waypoint_set, int button) { + if (button != 3) + return; + button = 2; + canvas_view->on_waypoint_clicked_canvasview(value_desc, waypoint_set, button); + }); + + curves->signal_waypoint_double_clicked().connect([=](synfigapp::ValueDesc value_desc, std::set> waypoint_set, int button) { + if (button != 1) + return; + button = -1; + canvas_view->on_waypoint_clicked_canvasview(value_desc, waypoint_set, button); + }); + canvas_view->set_ext_widget(get_name(),curves); } diff --git a/synfig-studio/src/gui/mainwindow.cpp b/synfig-studio/src/gui/mainwindow.cpp index 8eb3246..5df031a 100644 --- a/synfig-studio/src/gui/mainwindow.cpp +++ b/synfig-studio/src/gui/mainwindow.cpp @@ -272,7 +272,7 @@ bool MainWindow::on_key_press_event(GdkEventKey* key_event) { Gtk::Widget * widget = get_focus(); - if (widget && (dynamic_cast(widget) || dynamic_cast(widget))) { + if (widget && (dynamic_cast(widget) || dynamic_cast(widget) || dynamic_cast(widget))) { bool handled = gtk_window_propagate_key_event(this->gobj(), key_event); if (handled) return true; diff --git a/synfig-studio/src/gui/selectdraghelper.h b/synfig-studio/src/gui/selectdraghelper.h new file mode 100644 index 0000000..06b0161 --- /dev/null +++ b/synfig-studio/src/gui/selectdraghelper.h @@ -0,0 +1,844 @@ +/* === S Y N F I G ========================================================= */ +/*! \file selectdraghelper.h +** \brief Helper to allow to select and drag items in a widget, eg. DrawingArea +** +** $Id$ +** +** \legal +** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley +** Copyright (c) 2019 Rodolfo R Gomes +** +** This package is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License as +** published by the Free Software Foundation; either version 2 of +** the License, or (at your option) any later version. +** +** This package is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** \endlegal +*/ +/* ========================================================================= */ + +#ifndef SYNFIG_STUDIO_SELECTDRAGHELPER_H +#define SYNFIG_STUDIO_SELECTDRAGHELPER_H + +#include +#include +#include +#include + +#include +#include +#include "app.h" + +namespace synfigapp { +namespace Action { +class PassiveGrouper; +} +} + +namespace studio { + +template +class SelectDragHelper +{ +public: + enum State {POINTER_NONE, POINTER_DRAGGING, POINTER_SELECTING, POINTER_PANNING}; + +private: + etl::handle canvas_interface; + const std::string drag_action_name; + synfigapp::Action::PassiveGrouper *action_group_drag; + + bool made_dragging_move; + bool dragging_started_by_key; + + T hovered_item; + std::vector selected_items; + const T* active_item; + + State pointer_state; + int pointer_tracking_start_x, pointer_tracking_start_y; + int last_pointer_x, last_pointer_y; + Gdk::Point active_item_start_position; + + void start_tracking_pointer(int x, int y); + void start_tracking_pointer(double x, double y); + + void start_dragging(const T *pointed_item); + void finish_dragging(); + void cancel_dragging(); + + bool process_key_press_event(GdkEventKey *event); + bool process_key_release_event(GdkEventKey *event); + bool process_button_double_press_event(GdkEventButton *event); + bool process_button_press_event(GdkEventButton *event); + bool process_button_release_event(GdkEventButton *event); + bool process_motion_event(GdkEventMotion *event); + bool process_scroll_event(GdkEventScroll *event); + + sigc::signal signal_selection_changed_; + sigc::signal signal_hovered_item_changed_; + + sigc::signal signal_zoom_in_requested_; + sigc::signal signal_zoom_out_requested_; + + sigc::signal signal_scroll_up_requested_; + sigc::signal signal_scroll_down_requested_; + + sigc::signal signal_panning_requested_; + + sigc::signal signal_redraw_needed_; + + sigc::signal signal_focus_requested_; + + sigc::signal signal_drag_started_; + sigc::signal signal_drag_canceled_; + sigc::signal signal_drag_finished_; + + sigc::signal signal_item_clicked_; + sigc::signal signal_item_double_clicked_; + + bool box_selection_enabled = true; + bool multiple_selection_enabled = true; + bool scroll_enabled = false; + bool zoom_enabled = false; + bool pan_enabled = false; + bool drag_enabled = true; + +public: + SelectDragHelper(const char *drag_action_name); + virtual ~SelectDragHelper() {delete action_group_drag;} + + void set_canvas_interface(etl::handle canvas_interface); + etl::handle& get_canvas_interface(); + + /// The item whose pointer/mouse is hovering over + const T& get_hovered_item() const; + /// Provides a list of selected items + std::vector get_selected_items(); + /// Check if an item is selected + bool is_selected(const T& item) const; + /// The selected item user started to drag + const T* get_active_item() const; + + State get_state() const; + /// The point where user clicked + void get_initial_tracking_point(int &px, int &py) const; + /// The point where active item was initially placed + void get_active_item_initial_point(int &px, int &py) const; + + //! Retrieve the position of a given item + //! @param[out] position the location the provided item + virtual void get_item_position(const T &item, Gdk::Point &position) = 0; + + //! Retrieve the item placed at a point + //! @param[out] item the item in the given position + //! @return true if an item was found, false otherwise + virtual bool find_item_at_position(int pos_x, int pos_y, T & item) = 0; + + //! Retrieve all items inside a rectangular area + //! @param rect the rectangle area + //! @param[out] the list of items contained inside rect + //! @return true if any item was found, false otherwise + virtual bool find_items_in_rect(Gdk::Rectangle rect, std::vector & list) = 0; + + //! Retrieve all items in collection + //! @param[out] items the complete item list + virtual void get_all_items(std::vector & items) = 0; + + void drag_to(int pointer_x, int pointer_y); + //! Do drag selected items + /*! + * @param total_dx total pointer/mouse displacement along x-axis since drag start + * @param total_dy total pointer/mouse displacement along y-axis since drag start + * @param by_keys if the dragging action is being done by key pressing + */ + virtual void delta_drag(int total_dx, int total_dy, bool by_keys) = 0; + + //! Call this method to process mouse/pointer/keyboard events to map them + //! to select/drag/panning/zoom/scrolling behaviours + bool process_event(GdkEvent *event); + + void refresh(); + void clear(); + + void select_all_items(); + + bool get_box_selection_enabled() const; + void set_box_selection_enabled(bool value); + + bool get_multiple_selection_enabled() const; + void set_multiple_selection_enabled(bool value); + + bool get_scroll_enabled() const; + void set_scroll_enabled(bool value); + + bool get_zoom_enabled() const; + void set_zoom_enabled(bool value); + + bool get_pan_enabled() const; + void set_pan_enabled(bool value); + + bool get_drag_enabled() const; + void set_drag_enabled(bool value); + + sigc::signal& signal_selection_changed() { return signal_selection_changed_; } + sigc::signal& signal_hovered_item_changed() { return signal_hovered_item_changed_; } + + sigc::signal& signal_zoom_in_requested() { return signal_zoom_in_requested_; } + sigc::signal& signal_zoom_out_requested() { return signal_zoom_out_requested_; } + + sigc::signal& signal_scroll_up_requested() { return signal_scroll_up_requested_; } + sigc::signal& signal_scroll_down_requested() { return signal_scroll_down_requested_; } + + sigc::signal& signal_panning_requested() { return signal_panning_requested_; } + + sigc::signal& signal_redraw_needed() { return signal_redraw_needed_; } + + sigc::signal& signal_focus_requested() { return signal_focus_requested_; } + + sigc::signal& signal_drag_started() { return signal_drag_started_; } + sigc::signal& signal_drag_canceled() { return signal_drag_canceled_; } + sigc::signal& signal_drag_finished() { return signal_drag_finished_; } + + sigc::signal& signal_item_clicked() { return signal_item_clicked_; } + sigc::signal& signal_item_double_clicked() { return signal_item_double_clicked_; } +}; + + +template +SelectDragHelper::SelectDragHelper(const char* drag_action_name) + : drag_action_name(drag_action_name), action_group_drag(nullptr), active_item(nullptr), pointer_state(POINTER_NONE) +{ +} + +template +void SelectDragHelper::set_canvas_interface(etl::handle canvas_interface) +{ + if (pointer_state == POINTER_DRAGGING) { + cancel_dragging(); + } + this->canvas_interface = canvas_interface; +} + +template +etl::handle& SelectDragHelper::get_canvas_interface() +{ + return canvas_interface; +} + +template +const T& SelectDragHelper::get_hovered_item() const +{ + return hovered_item; +} + +template +std::vector SelectDragHelper::get_selected_items() +{ + const size_t nselection = selected_items.size(); + std::vector r; + r.reserve(nselection); + for (size_t n = 0; n < nselection; n++) + r.push_back(&selected_items[n]); + return r; +} + +template +bool SelectDragHelper::is_selected(const T& item) const { + return std::find(selected_items.begin(), selected_items.end(), item) != selected_items.end(); +} + +template +const T* SelectDragHelper::get_active_item() const { + return active_item; +} + +template +typename SelectDragHelper::State SelectDragHelper::get_state() const +{ + return pointer_state; +} + +template +void SelectDragHelper::get_initial_tracking_point(int& px, int& py) const +{ + px = pointer_tracking_start_x; + py = pointer_tracking_start_y; +} + +template +void SelectDragHelper::get_active_item_initial_point(int& px, int& py) const +{ + px = active_item_start_position.get_x(); + py = active_item_start_position.get_y(); +} + + +template +bool +SelectDragHelper::process_event(GdkEvent *event) +{ + switch(event->type) { + case GDK_SCROLL: + return process_scroll_event(&event->scroll); + case GDK_MOTION_NOTIFY: + return process_motion_event(&event->motion); + case GDK_2BUTTON_PRESS: + return process_button_double_press_event(&event->button); + case GDK_BUTTON_PRESS: + return process_button_press_event(&event->button); + case GDK_BUTTON_RELEASE: + return process_button_release_event(&event->button); + case GDK_KEY_RELEASE: + return process_key_release_event(&event->key); + case GDK_KEY_PRESS: + return process_key_press_event(&event->key); + default: + break; + } + + return false; +} + +template +bool SelectDragHelper::process_key_press_event(GdkEventKey* event) +{ + switch (event->keyval) { + case GDK_KEY_Up: + case GDK_KEY_Down: { + if (!drag_enabled) + return false; + if (selected_items.size() == 0) + break; + if (pointer_state != POINTER_DRAGGING) { + start_dragging(&selected_items.front()); + dragging_started_by_key = true; + } + int delta = 1; + if (event->state & GDK_SHIFT_MASK) + delta = 10; + if (event->keyval == GDK_KEY_Up) + delta = -delta; + delta_drag(0, delta, true); + return true; + } + case GDK_KEY_Left: + case GDK_KEY_Right: { + if (!drag_enabled) + return false; + if (selected_items.size() == 0) + break; + if (pointer_state != POINTER_DRAGGING) { + start_dragging(&selected_items.front()); + dragging_started_by_key = true; + } + int delta = 1; + if (event->state & GDK_SHIFT_MASK) + delta *= 10; + if (event->keyval == GDK_KEY_Left) + delta = -delta; + delta_drag(delta, 0, true); + return true; + } + case GDK_KEY_a: { + if ((event->state & Gdk::CONTROL_MASK) == Gdk::CONTROL_MASK) { + // ctrl a + cancel_dragging(); + select_all_items(); + return true; + } + break; + } + case GDK_KEY_d: { + if ((event->state & Gdk::CONTROL_MASK) == Gdk::CONTROL_MASK) { + // ctrl d + cancel_dragging(); + selected_items.clear(); + signal_redraw_needed().emit(); + signal_selection_changed().emit(); + return true; + } + break; + } + } + return false; +} + +template +bool SelectDragHelper::process_key_release_event(GdkEventKey* event) +{ + switch (event->keyval) { + case GDK_KEY_Escape: { + // cancel/undo current action + if (pointer_state != POINTER_NONE) { + cancel_dragging(); + pointer_state = POINTER_NONE; + signal_redraw_needed().emit(); + return true; + } + } + case GDK_KEY_Up: + case GDK_KEY_Down: + case GDK_KEY_Left: + case GDK_KEY_Right: { + if (pointer_state == POINTER_DRAGGING) { + if (dragging_started_by_key) + finish_dragging(); + dragging_started_by_key = false; + return true; + } + } + } + return false; +} + +template +bool SelectDragHelper::process_button_double_press_event(GdkEventButton* event) +{ + T pointed_item; + find_item_at_position(event->x, event->y, pointed_item); + if (pointed_item.is_valid()) { + int pointer_x = std::trunc(event->x); + int pointer_y = std::trunc(event->y); + signal_item_double_clicked().emit(pointed_item, event->button, Gdk::Point(pointer_x, pointer_y)); + return true; + } + return false; +} + +template +bool SelectDragHelper::process_button_press_event(GdkEventButton* event) +{ + bool some_action_done = false; + + signal_focus_requested().emit(); + if (event->button == 3) { + // cancel/undo current action + if (pointer_state != POINTER_NONE) { + cancel_dragging(); + pointer_state = POINTER_NONE; + signal_redraw_needed().emit(); + some_action_done = true; + } + } else if (event->button == 1) { + if (pointer_state == POINTER_NONE) { + start_tracking_pointer(event->x, event->y); + T pointed_item; + find_item_at_position(pointer_tracking_start_x, pointer_tracking_start_y, pointed_item); + if (pointed_item.is_valid()) { + auto already_selection_it = std::find(selected_items.begin(), selected_items.end(), pointed_item); + bool is_already_selected = already_selection_it != selected_items.end(); + bool using_key_modifiers = (event->state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) != 0; + if (using_key_modifiers && box_selection_enabled && multiple_selection_enabled) { + pointer_state = POINTER_SELECTING; + } else { + T* pointed_item_ptr = nullptr; + if (is_already_selected) { + pointed_item_ptr = &*already_selection_it; + } else { + selected_items.clear(); + selected_items.push_back(pointed_item); + pointed_item_ptr = &selected_items.front(); + signal_selection_changed().emit(); + } + if (drag_enabled) { + start_dragging(pointed_item_ptr); + dragging_started_by_key = false; + pointer_state = POINTER_DRAGGING; + } + } + some_action_done = true; + } else { + if (box_selection_enabled && multiple_selection_enabled) { + pointer_state = POINTER_SELECTING; + some_action_done = true; + } + } + } + } else if (event->button == 2) { + if (pointer_state == POINTER_DRAGGING) + finish_dragging(); + start_tracking_pointer(event->x, event->y); + pointer_state = POINTER_PANNING; + some_action_done = true; + } + + { + T pointed_item; + find_item_at_position(event->x, event->y, pointed_item); + if (pointed_item.is_valid()) { + int pointer_x = std::trunc(event->x); + int pointer_y = std::trunc(event->y); + signal_item_clicked().emit(pointed_item, event->button, Gdk::Point(pointer_x, pointer_y)); + } + } + return some_action_done; +} + +template +bool SelectDragHelper::process_button_release_event(GdkEventButton* event) +{ + int pointer_x = std::trunc(event->x); + int pointer_y = std::trunc(event->y); + + if (event->button == 1) { + bool selection_changed = false; + + if (pointer_state == POINTER_SELECTING) { + std::vector circled_items; + int x0 = std::min(pointer_tracking_start_x, pointer_x); + int width = std::abs(pointer_tracking_start_x - pointer_x); + int y0 = std::min(pointer_tracking_start_y, pointer_y); + int height = std::abs(pointer_tracking_start_y - pointer_y); + if (width < 1 && height < 1) { + width = 1; + height = 1; + } + Gdk::Rectangle rect(x0, y0, width, height); + bool found = find_items_in_rect(rect, circled_items); + if (!found) { + if (selected_items.size() > 0 && (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == 0) { + selection_changed = true; + selected_items.clear(); + } + } else { + if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) { + // toggle selection status of each point in rectangle + for (const T& cp : circled_items) { + auto already_selection_it = std::find(selected_items.begin(), selected_items.end(), cp); + bool already_selected = already_selection_it != selected_items.end(); + if (already_selected) { + selected_items.erase(already_selection_it); + selection_changed = true; + } else { + selected_items.push_back(cp); + selection_changed = true; + } + } + } else if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) { + // add to selection, if it aren't yet + for (const T& cp : circled_items) { + auto already_selection_it = std::find(selected_items.begin(), selected_items.end(), cp); + bool already_selected = already_selection_it != selected_items.end(); + if (!already_selected) { + selected_items.push_back(cp); + selection_changed = true; + } + } + } else { + selected_items.clear(); + selected_items = circled_items; + selection_changed = true; + } + } + } + else if (pointer_state == POINTER_DRAGGING) { + if (event->button == 1) { + if (made_dragging_move) + finish_dragging(); + else { + T saved_active_point = *active_item; + selected_items.clear(); + selected_items.push_back(saved_active_point); + selection_changed = true; + cancel_dragging(); + } + } + } + + if (selection_changed) { + signal_selection_changed().emit(); + signal_redraw_needed().emit(); + } + } + else if (event->button == 2) { + if (pointer_state == POINTER_PANNING) + pointer_state = POINTER_NONE; + } + + if (event->button == 1 || event->button == 3) { + if (pointer_state != POINTER_NONE) { + pointer_state = POINTER_NONE; + signal_redraw_needed().emit(); + } + } + + return false; +} + +template +bool SelectDragHelper::process_motion_event(GdkEventMotion* event) +{ + bool processed = false; + auto previous_hovered_point = hovered_item; + hovered_item.invalidate(); + + int pointer_x = std::trunc(event->x); + int pointer_y = std::trunc(event->y); + if (pointer_state != POINTER_DRAGGING) + find_item_at_position(pointer_x, pointer_y, hovered_item); + + if (previous_hovered_point != hovered_item) { + signal_hovered_item_changed().emit(); + signal_redraw_needed().emit(); + } + + if (pointer_state == POINTER_DRAGGING && !dragging_started_by_key) { + guint any_pointer_button = Gdk::BUTTON1_MASK |Gdk::BUTTON2_MASK | Gdk::BUTTON3_MASK; + if ((event->state & any_pointer_button) == 0) { + // If some modal window is called, we lose the button-release event... + cancel_dragging(); + } else { + bool axis_lock = event->state & Gdk::SHIFT_MASK; + if (axis_lock) { + int dx = pointer_x - pointer_tracking_start_x; + int dy = pointer_y - pointer_tracking_start_y; + if (std::abs(dy) > std::abs(dx)) + pointer_x = pointer_tracking_start_x; + else + pointer_y = pointer_tracking_start_y; + } + drag_to(pointer_x, pointer_y); + } + processed = true; + } + else if (pointer_state == POINTER_PANNING) { + const int dx = pointer_x - last_pointer_x; + const int dy = pointer_y - last_pointer_y; + const int total_dx = pointer_x - pointer_tracking_start_x; + const int total_dy = pointer_y - pointer_tracking_start_y; + + signal_panning_requested().emit(dx, dy, total_dx, total_dy); + processed = true; + } + + if (pointer_state != POINTER_NONE) { + signal_redraw_needed().emit(); + } + + last_pointer_x = pointer_x; + last_pointer_y = pointer_y; + return processed; +} + +template +bool SelectDragHelper::process_scroll_event(GdkEventScroll* event) +{ + switch(event->direction) { + case GDK_SCROLL_UP: + case GDK_SCROLL_RIGHT: { + if ((event->state & GDK_CONTROL_MASK) && zoom_enabled) { + // Ctrl+scroll , perform zoom in + signal_zoom_in_requested().emit(); + } else { + if (!scroll_enabled) + return false; + // Scroll up + signal_scroll_up_requested().emit(); + } + return true; + } + case GDK_SCROLL_DOWN: + case GDK_SCROLL_LEFT: { + if ((event->state & GDK_CONTROL_MASK) && zoom_enabled) { + // Ctrl+scroll , perform zoom out + signal_zoom_out_requested().emit(); + } else { + if (!scroll_enabled) + return false; + // Scroll down + signal_scroll_down_requested().emit(); + } + return true; + } + default: + break; + } + return false; +} + +template +void SelectDragHelper::refresh() { + hovered_item.invalidate(); +} + +template +void SelectDragHelper::clear() { + if (pointer_state == POINTER_DRAGGING) { + cancel_dragging(); + } + hovered_item.invalidate(); + if (!selected_items.empty()) { + selected_items.clear(); + signal_selection_changed().emit(); + } +} + +template +void SelectDragHelper::start_tracking_pointer(double x, double y) +{ + pointer_tracking_start_x = std::trunc(x); + pointer_tracking_start_y = std::trunc(y); + last_pointer_x = pointer_tracking_start_x; + last_pointer_y = pointer_tracking_start_y; +} + +template +void SelectDragHelper::start_dragging(const T* pointed_item) +{ + made_dragging_move = false; + active_item = pointed_item; + get_item_position(*active_item, active_item_start_position); + + if (canvas_interface) { + action_group_drag = new synfigapp::Action::PassiveGrouper(canvas_interface->get_instance().get(), drag_action_name); + } + + pointer_state = POINTER_DRAGGING; + + signal_drag_started().emit(); +} + +template +void SelectDragHelper::drag_to(int pointer_x, int pointer_y) +{ + made_dragging_move = true; + + int pointer_dx = pointer_x - pointer_tracking_start_x; + int pointer_dy = pointer_y - pointer_tracking_start_y; + delta_drag(pointer_dx, pointer_dy, false); +} + +template +void SelectDragHelper::finish_dragging() +{ + delete action_group_drag; + action_group_drag = nullptr; + + pointer_state = POINTER_NONE; + + signal_drag_finished().emit(); +} + +template +void SelectDragHelper::cancel_dragging() +{ + if (pointer_state != POINTER_DRAGGING) + return; + + // Sadly group->cancel() just remove PassiverGroup indicator, not its actions, from stack + + if (action_group_drag) { + bool has_any_content = 0 < action_group_drag->get_depth(); + delete action_group_drag; + action_group_drag = nullptr; + if (has_any_content && canvas_interface) { + canvas_interface->get_instance()->undo(); + canvas_interface->get_instance()->clear_redo_stack(); + } + } + + pointer_state = POINTER_NONE; + signal_drag_canceled().emit(); + signal_redraw_needed().emit(); +} + +template +void SelectDragHelper::select_all_items() +{ + cancel_dragging(); + selected_items.clear(); + std::vector all_items; + get_all_items(all_items); + selected_items = std::move(all_items); + signal_selection_changed().emit(); +} + +template +bool SelectDragHelper::get_box_selection_enabled() const +{ + return box_selection_enabled; +} + +template +void SelectDragHelper::set_box_selection_enabled(bool value) +{ + box_selection_enabled = value; +} + +template +bool SelectDragHelper::get_multiple_selection_enabled() const +{ + return multiple_selection_enabled; +} + +template +void SelectDragHelper::set_multiple_selection_enabled(bool value) +{ + multiple_selection_enabled = value; + if (!multiple_selection_enabled && selected_items.size() > 1) { + selected_items.resize(1); + if (active_item && active_item != &selected_items.front()) { + cancel_dragging(); + } + } +} + +template +bool SelectDragHelper::get_scroll_enabled() const +{ + return scroll_enabled; +} + +template +void SelectDragHelper::set_scroll_enabled(bool value) +{ + scroll_enabled = value; +} + +template +bool SelectDragHelper::get_zoom_enabled() const +{ + return zoom_enabled; +} + +template +void SelectDragHelper::set_zoom_enabled(bool value) +{ + zoom_enabled = value; +} + +template +bool SelectDragHelper::get_pan_enabled() const +{ + return pan_enabled; +} + +template +void SelectDragHelper::set_pan_enabled(bool value) +{ + pan_enabled = value; +} + +template +bool SelectDragHelper::get_drag_enabled() const +{ + return drag_enabled; +} + +template +void SelectDragHelper::set_drag_enabled(bool value) +{ + drag_enabled = value; + if (!drag_enabled) { + cancel_dragging(); + } +} + +}; + +#endif // SYNFIG_STUDIO_SELECTDRAGHELPER_H diff --git a/synfig-studio/src/gui/timeplotdata.cpp b/synfig-studio/src/gui/timeplotdata.cpp new file mode 100644 index 0000000..ab83f13 --- /dev/null +++ b/synfig-studio/src/gui/timeplotdata.cpp @@ -0,0 +1,246 @@ +/* === S Y N F I G ========================================================= */ +/*! \file widget_curves.cpp +** \brief Template File +** +** $Id$ +** +** \legal +** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley +** ......... ... 2019 Rodolfo R. Gomes +** +** This package is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License as +** published by the Free Software Foundation; either version 2 of +** the License, or (at your option) any later version. +** +** This package is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** \endlegal +*/ +/* ========================================================================= */ + +/* === H E A D E R S ======================================================= */ + +#ifdef USING_PCH +# include "pch.h" +#else +# ifdef HAVE_CONFIG_H +# include +# endif + +# include "timeplotdata.h" +# include + +#endif + +/* === U S I N G =========================================================== */ + +/* === M A C R O S ========================================================= */ + +/* === G L O B A L S ======================================================= */ + +/* === P R O C E D U R E S ================================================= */ + +/* === C L A S S E S ======================================================= */ + +namespace studio { + +TimePlotData::TimePlotData(Gtk::Widget& widget, Glib::RefPtr vertical_adjustment) : + invalid(true), + extra_margin(0), + has_vertical(false), + widget(widget), + vertical_adjustment(vertical_adjustment) +{ + widget_resized = widget.signal_configure_event().connect( + sigc::mem_fun(*this, &TimePlotData::on_widget_resize) ); + + if (vertical_adjustment) { + vertical_changed = vertical_adjustment->signal_changed().connect( + sigc::mem_fun(*this, &TimePlotData::recompute_vertical) ); + vertical_value_changed = vertical_adjustment->signal_value_changed().connect( + sigc::mem_fun(*this, &TimePlotData::recompute_vertical) ); + recompute_vertical(); + } +} + +TimePlotData::~TimePlotData() +{ + widget_resized.disconnect(); + time_model_changed.disconnect(); + vertical_changed.disconnect(); + vertical_value_changed.disconnect(); +} + +void +TimePlotData::set_time_model(const etl::handle& time_model) +{ + if (this->time_model == time_model) + return; + + time_model_changed.disconnect(); + + this->time_model = time_model; + + if (time_model) { + time_model_changed = this->time_model->signal_changed().connect( + sigc::mem_fun(*this, &TimePlotData::recompute_time_bounds) ); + invalid = false; + } else { + invalid = true; + } + + recompute_time_bounds(); +} + +void +TimePlotData::set_extra_time_margin(double margin) +{ + extra_margin = margin; + + recompute_extra_time(); +} + +bool +TimePlotData::on_widget_resize(GdkEventConfigure*) +{ + recompute_geometry_data(); + return false; +} + +void +TimePlotData::recompute_time_bounds() +{ + if (!time_model) { + time = lower = upper = 0; + invalid = true; + widget.queue_draw(); + return; + } + time = time_model->get_time(); + lower = time_model->get_visible_lower(); + upper = time_model->get_visible_upper(); + + if (lower >= upper) { + invalid = true; + widget.queue_draw(); + return; + } + + invalid = false; + recompute_geometry_data(); // lower and upper change other fields +} + +void +TimePlotData::recompute_geometry_data() +{ + k = widget.get_width()/(upper - lower); + dt = 1.0/k; + + if (has_vertical) { + range_k = widget.get_height()/(range_upper - range_lower); + } + + recompute_extra_time(); // k (and lower and upper) changes extra_time +} + +void +TimePlotData::recompute_extra_time() +{ + extra_time = extra_margin/k; + lower_ex = lower - extra_time; + upper_ex = upper + extra_time; + + widget.queue_draw(); +} + +void +TimePlotData::recompute_vertical() +{ + if (!vertical_adjustment) { + has_vertical = false; + return; + } + range_lower = vertical_adjustment->get_value(); + range_upper = range_lower + vertical_adjustment->get_page_size(); + range_k = widget.get_height()/(range_upper - range_lower); + has_vertical = true; + widget.queue_draw(); +} + +bool +TimePlotData::is_invalid() const +{ + return invalid; +} + +bool +TimePlotData::is_time_visible(const synfig::Time& t) const +{ + return t >= lower && t <= upper; +} + +bool +TimePlotData::is_time_visible_extra(const synfig::Time& t) const +{ + return t >= lower_ex && t <= upper_ex; +} + +bool +TimePlotData::is_y_visible(synfig::Real y) const +{ + return y >= range_lower && y <= range_upper; +} + +int +TimePlotData::get_pixel_t_coord(const synfig::Time& t) const +{ + return etl::round_to_int((t - lower) * k); +} + +double +TimePlotData::get_double_pixel_t_coord(const synfig::Time& t) const +{ + return round((t - lower) * k); +} + +int +TimePlotData::get_pixel_y_coord(synfig::Real y) const +{ + return etl::round_to_int(-(y + range_lower) * range_k); +} + +synfig::Time +TimePlotData::get_t_from_pixel_coord(double pixel) const +{ + return lower + synfig::Time(pixel/k); +} + +double TimePlotData::get_y_from_pixel_coord(double pixel) const +{ + return -(range_lower + pixel / range_k); +} + +synfig::Time studio::TimePlotData::get_delta_t_from_delta_pixel_coord(int delta_pixel) const +{ + return synfig::Time(delta_pixel/k); +} + +double TimePlotData::get_delta_y_from_delta_pixel_coord(int delta_pixel) const +{ + return -(delta_pixel / range_k); +} + +int TimePlotData::get_delta_pixel_from_delta_t_coord(double delta_t) const +{ + return int(delta_t * k); +} + +int TimePlotData::get_delta_pixel_from_delta_y_coord(double delta_y) const +{ + return int(-delta_y * range_k); +} + +} diff --git a/synfig-studio/src/gui/timeplotdata.h b/synfig-studio/src/gui/timeplotdata.h new file mode 100644 index 0000000..ea9566f --- /dev/null +++ b/synfig-studio/src/gui/timeplotdata.h @@ -0,0 +1,155 @@ +/* === S Y N F I G ========================================================= */ +/*! \file timeplotdata.h +** \brief Template Header +** +** $Id$ +** +** \legal +** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley +** ......... ... 2019 Rodolfo R. Gomes +** +** This package is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License as +** published by the Free Software Foundation; either version 2 of +** the License, or (at your option) any later version. +** +** This package is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** \endlegal +*/ +/* ========================================================================= */ + +/* === S T A R T =========================================================== */ + +#ifndef __SYNFIG_STUDIO_TIMEPLOTDATA_H +#define __SYNFIG_STUDIO_TIMEPLOTDATA_H + +/* === H E A D E R S ======================================================= */ + +#include "gui/timemodel.h" +#include + +#include +#include + +/* === M A C R O S ========================================================= */ + +/* === T Y P E D E F S ===================================================== */ + +/* === C L A S S E S & S T R U C T S ======================================= */ + +namespace studio { + +/// Helper class that connects widget geometry to visible timeline and vice versa +/** + * Some widgets show data that must be in sync to the project timeline, eg. + * the waypoint 'editor'. + * This class helps to map pixel coordinate <-> time, being updated automatically + * when the timeline changes (by zooming or scrolling, for example) or widget size + * changes. + * + * For widgets that uses another axis that can be scrolled and/or zoomed (like + * Widget_Curves), \ref vertical_adjustment should be used. + * + * \sa TimeModel + */ +class TimePlotData { + bool invalid; + +public: + etl::handle time_model; + + /// Current Time \sa TimeModel::get_time + synfig::Time time; + /// Start visible Time \sa TimeModel::get_visible_lower() + synfig::Time lower; + /// Final visible Time \sa TimeModel::get_visible_upper() + synfig::Time upper; + /// How many pixels per second + double k; + /// How long a pixel last. Inverse of \ref k + synfig::Time dt; + + /// How many extra pixels beyond regular bounds \sa set_extra_time_margin() + double extra_margin; + /// How long last the extra pixels + synfig::Time extra_time; + synfig::Time lower_ex; + synfig::Time upper_ex; + +private: + /// If vertical adjustment is set + bool has_vertical; + synfig::Real range_lower; + synfig::Real range_upper; + double range_k; + + sigc::connection widget_resized; + sigc::connection time_model_changed; + sigc::connection vertical_changed; + sigc::connection vertical_value_changed; + + Gtk::Widget &widget; + Glib::RefPtr vertical_adjustment; + +public: + /** + * \param widget The widget that must zoom and scroll in sync with the timeline + * \param vertical_adjustment If widget displays info in another axis that can be scrolled/zoomed + */ + TimePlotData(Gtk::Widget & widget, Glib::RefPtr vertical_adjustment = Glib::RefPtr()); + virtual ~TimePlotData(); + + void set_time_model(const etl::handle &time_model); + + /// Sets an extra margin, creating a new and wider range. \sa is_time_visible_extra() + void set_extra_time_margin(double margin); + + /// Checks whether this data is valid (it has valid size and valid time info) + bool is_invalid() const; + + /// If \ref t is in \ref lower - \ref upper range + bool is_time_visible(const synfig::Time & t) const; + /// If \ref t is in expanded range (\ref lower_ex - \ref upper_ex) + bool is_time_visible_extra(const synfig::Time & t) const; + + bool is_y_visible(synfig::Real y) const; + + /// What pixel time t is mapped to. Uses ::etl::round_to_int() + int get_pixel_t_coord(const synfig::Time & t) const; + + /// Similar to get_pixel_t_coord(), but rounded by regular round() + /** Maybe it was an error and should be replaced by get_pixel_t_coord() */ + double get_double_pixel_t_coord(const synfig::Time & t) const; + + int get_pixel_y_coord(synfig::Real y) const; + + /// What time a pixel represents. + synfig::Time get_t_from_pixel_coord(double pixel) const; + + double get_y_from_pixel_coord(double pixel) const; + + synfig::Time get_delta_t_from_delta_pixel_coord(int delta_pixel) const; + + double get_delta_y_from_delta_pixel_coord(int delta_pixel) const; + + int get_delta_pixel_from_delta_t_coord(double delta_t) const; + + int get_delta_pixel_from_delta_y_coord(double delta_y) const; + +private: + bool on_widget_resize(GdkEventConfigure * /*configure*/); + + void recompute_time_bounds(); + void recompute_geometry_data(); + void recompute_extra_time(); + void recompute_vertical(); +}; // END of class TimePlotData + +}; // END of namespace studio + +/* === E N D =============================================================== */ + +#endif // TIMEPLOTDATA_H diff --git a/synfig-studio/src/gui/waypointrenderer.cpp b/synfig-studio/src/gui/waypointrenderer.cpp new file mode 100644 index 0000000..44eef07 --- /dev/null +++ b/synfig-studio/src/gui/waypointrenderer.cpp @@ -0,0 +1,396 @@ +/* === S Y N F I G ========================================================= */ +/*! \file waypointrenderer.cpp +** +** $Id$ +** +** \legal +** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley +** ......... ... 2019 Rodolfo R. Gomes +** +** This package is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License as +** published by the Free Software Foundation; either version 2 of +** the License, or (at your option) any later version. +** +** This package is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** \endlegal +*/ +/* ========================================================================= */ + +/* === H E A D E R S ======================================================= */ + +#ifdef USING_PCH +# include "pch.h" +#else +# ifdef HAVE_CONFIG_H +# include +# endif + +#include "waypointrenderer.h" + +#include + +#include + +#include +#include +#endif + +/* === U S I N G =========================================================== */ + +using namespace synfig; +using namespace synfigapp; + +/* === M A C R O S ========================================================= */ + +/* === G L O B A L S ======================================================= */ + +/* === P R O C E D U R E S ================================================= */ + +/* === C L A S S E S ======================================================= */ + +namespace studio { + +static Gdk::RGBA +get_interp_color(Interpolation x) +{ + switch(x) { + case INTERPOLATION_TCB: + return Gdk::RGBA("#73d216"); + case INTERPOLATION_LINEAR: + return Gdk::RGBA("#edd400"); + case INTERPOLATION_CONSTANT: + return Gdk::RGBA("#cc0000"); + case INTERPOLATION_HALT: + return Gdk::RGBA("#3465a4"); + case INTERPOLATION_MANUAL: + return Gdk::RGBA("#75507b"); + case INTERPOLATION_CLAMPED: + return Gdk::RGBA("#c17d11"); + case INTERPOLATION_UNDEFINED: + default: + break; + } + return Gdk::RGBA("#555753"); +} + +static Gdk::RGBA +color_darken(Gdk::RGBA x, float amount) +{ + x.set_red(x.get_red() * amount); + x.set_green(x.get_green() * amount); + x.set_blue(x.get_blue() * amount); + return x; +} + +static Gdk::RGBA +color_shift(Gdk::RGBA x, double amount) +{ + x.set_red(x.get_red() + amount); + x.set_green(x.get_green() + amount); + x.set_blue(x.get_blue() + amount); + return x; +} + +static Gdk::RGBA +get_black(bool hover) +{ + if (!hover) + return Gdk::RGBA("#2e3436"); // it's black, trust me + return Gdk::RGBA("#4e5456"); +} + +void +WaypointRenderer::render_time_point_to_window( + const Cairo::RefPtr &cr, + const Gdk::Rectangle& area, + const TimePoint &tp, + bool selected, + bool hover) +{ + const Gdk::RGBA black = get_black(hover); + + if(selected) + cr->set_line_width(2.0); + else + cr->set_line_width(1.0); + + Gdk::RGBA color; + +/*- BEFORE ------------------------------------- */ + + color=get_interp_color(tp.get_before()); + color=color_darken(color,1.0f); + if(selected)color=color_darken(color,1.3f); + if(hover) color = color_shift(color, 0.2); + cr->set_source_rgb(color.get_red(),color.get_green(),color.get_blue()); + + switch(tp.get_before()) + { + case INTERPOLATION_TCB: + cr->save(); + cr->translate(area.get_x(), area.get_y()); + cr->scale(area.get_width(), area.get_height()); + cr->move_to(0.5, 1); + cr->arc(0.5, 0.5, 0.5, 90*M_PI/180.0, 270*M_PI/180.0); + cr->fill_preserve(); + cr->restore(); + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->stroke(); + break; + + case INTERPOLATION_HALT: + cr->save(); + cr->translate(area.get_x(), area.get_y()); + cr->scale(area.get_width(), area.get_height()*2); + cr->move_to(0.5, 0.5); + cr->arc(0.5, 0.5, 0.5, 180*M_PI/180.0, 270*M_PI/180.0); + cr->fill(); + cr->arc(0.5, 0.5, 0.5, 180*M_PI/180.0, 270*M_PI/180.0); + cr->restore(); + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->stroke(); + + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->move_to(area.get_x(),area.get_y()+area.get_height()); + cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); + cr->stroke(); + break; + + case INTERPOLATION_LINEAR: + cr->save(); + cr->move_to(area.get_x()+area.get_width()/2,area.get_y()); + cr->line_to(area.get_x(),area.get_y()+area.get_height()); + cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); + cr->fill_preserve(); + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->stroke(); + cr->restore(); + break; + + case INTERPOLATION_CONSTANT: + cr->save(); + cr->move_to(area.get_x()+area.get_width()/2,area.get_y()); + cr->line_to(area.get_x()+area.get_width()/4,area.get_y()); + cr->line_to(area.get_x()+area.get_width()/4,area.get_y()+area.get_height()/2); + cr->line_to(area.get_x(),area.get_y()+area.get_height()/2); + cr->line_to(area.get_x(),area.get_y()+area.get_height()); + cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); + cr->fill_preserve(); + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->stroke(); + cr->restore(); + break; + + case INTERPOLATION_CLAMPED: + cr->save(); + cr->move_to(area.get_x()+area.get_width()/2,area.get_y()); + cr->line_to(area.get_x(),area.get_y()+area.get_height()/2); + cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); + cr->fill_preserve(); + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->stroke(); + cr->restore(); + break; + + default: + cr->save(); + cr->line_to(area.get_x()+area.get_width()/2,area.get_y()); + cr->line_to(area.get_x()+area.get_width()/3,area.get_y()); + cr->line_to(area.get_x(),area.get_y()+area.get_height()/3); + cr->line_to(area.get_x(),area.get_y()+area.get_height()-area.get_height()/3); + cr->line_to(area.get_x()+area.get_width()/3,area.get_y()+area.get_height()); + cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); + cr->fill_preserve(); + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->stroke(); + cr->restore(); + break; + } + +/*- AFTER -------------------------------------- */ + + color=get_interp_color(tp.get_after()); + color=color_darken(color,0.8f); + if(selected)color=color_darken(color,1.3f); + if(hover) color = color_shift(color, 0.2); + cr->set_source_rgb(color.get_red(),color.get_green(),color.get_blue()); + + switch(tp.get_after()) + { + case INTERPOLATION_TCB: + cr->save(); + cr->translate(area.get_x(), area.get_y()); + cr->scale(area.get_width(), area.get_height()); + cr->arc(0.5, 0.5, 0.5, -90*M_PI/180.0, 90*M_PI/180.0); + cr->fill_preserve(); + cr->restore(); + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->stroke(); + break; + + case INTERPOLATION_HALT: + cr->save(); + cr->translate(area.get_x(), area.get_y()); + cr->scale(area.get_width(), area.get_height()*2); + cr->move_to(0.5, 0.0); + cr->arc(0.5, 0.0, 0.5, 0*M_PI/180.0, 90*M_PI/180.0); + cr->fill(); + cr->arc(0.5, 0.0, 0.5, 0*M_PI / 180.0, 90*M_PI / 180.0); + cr->restore(); + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->stroke(); + + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->move_to(area.get_x()+area.get_width()/2,area.get_y()); + cr->line_to(area.get_x()+area.get_width(),area.get_y()); + cr->stroke(); + break; + + case INTERPOLATION_LINEAR: + cr->save(); + cr->move_to(area.get_x()+area.get_width()/2,area.get_y()); + cr->line_to(area.get_x()+area.get_width(),area.get_y()); + cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); + cr->fill_preserve(); + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->stroke(); + cr->restore(); + break; + + case INTERPOLATION_CONSTANT: + cr->save(); + cr->move_to(area.get_x()+area.get_width()/2,area.get_y()); + cr->line_to(area.get_x()+area.get_width(),area.get_y()); + cr->line_to(area.get_x()+area.get_width(),area.get_y()+area.get_height()/2); + cr->line_to(area.get_x()+area.get_width()-area.get_width()/4,area.get_y()+area.get_height()/2); + cr->line_to(area.get_x()+area.get_width()-area.get_width()/4,area.get_y()+area.get_height()); + cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); + cr->fill_preserve(); + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->stroke(); + cr->restore(); + break; + + case INTERPOLATION_CLAMPED: + cr->save(); + cr->line_to(area.get_x()+area.get_width()/2,area.get_y()); + cr->line_to(area.get_x()+area.get_width(),area.get_y()+area.get_height()/2); + cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); + cr->fill_preserve(); + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->stroke(); + cr->restore(); + break; + + default: + cr->save(); + cr->line_to(area.get_x()+area.get_width()/2,area.get_y()); + cr->line_to(area.get_x()+area.get_width()-area.get_width()/3,area.get_y()); + cr->line_to(area.get_x()+area.get_width(),area.get_y()+area.get_height()/3); + cr->line_to(area.get_x()+area.get_width(),area.get_y()+area.get_height()-area.get_height()/3); + cr->line_to(area.get_x()+area.get_width()-area.get_width()/3,area.get_y()+area.get_height()); + cr->line_to(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()); + cr->fill_preserve(); + cr->set_source_rgb(black.get_red(),black.get_green(),black.get_blue()); + cr->stroke(); + cr->restore(); + break; + } +} + + +static Time +get_time_offset_from_vdesc(const ValueDesc &v) +{ +#ifdef ADJUST_WAYPOINTS_FOR_TIME_OFFSET + if (v.get_value_type() != type_canvas || getenv("SYNFIG_SHOW_CANVAS_PARAM_WAYPOINTS")) + return Time::zero(); + + if (!v.get_value().get(Canvas::Handle())) + return Time::zero(); + + if (!v.parent_is_layer()) + return Time::zero(); + + synfig::Layer::Handle layer = v.get_layer(); + + if (!etl::handle::cast_dynamic(layer)) + return Time::zero(); + + return layer->get_param("time_offset").get(Time()); +#else // ADJUST_WAYPOINTS_FOR_TIME_OFFSET + return synfig::Time::zero(); +#endif +} + +static Time +get_time_dilation_from_vdesc(const ValueDesc &v) +{ +#ifdef ADJUST_WAYPOINTS_FOR_TIME_OFFSET + if (v.get_value_type() != type_canvas || getenv("SYNFIG_SHOW_CANVAS_PARAM_WAYPOINTS")) + return Time(1.0); + + if (!v.get_value().get(Canvas::Handle())) + return Time(1.0); + + if (!v.parent_is_layer()) + return Time(1.0); + + Layer::Handle layer = v.get_layer(); + + if (!etl::handle::cast_dynamic(layer)) + return Time(1.0); + + return layer->get_param("time_dilation").get(Time()); +#else // ADJUST_WAYPOINTS_FOR_TIME_OFFSET + return Time(1.0); +#endif +} + +static const Node::time_set empty_time_set {}; + +const Node::time_set& +WaypointRenderer::get_times_from_valuedesc(const ValueDesc &v) +{ + if (v.get_value_type() == type_canvas && !getenv("SYNFIG_SHOW_CANVAS_PARAM_WAYPOINTS")) + if(Canvas::Handle canvasparam = v.get_value().get(Canvas::Handle())) + return canvasparam->get_times(); + + //we want a dynamic list entry to override the normal... + if (v.parent_is_value_node()) + if (ValueNode_DynamicList *parent_value_node = dynamic_cast(v.get_parent_value_node().get())) + return parent_value_node->list[v.get_index()].get_times(); + + if (ValueNode *base_value = v.get_value_node().get()) //don't render stuff if it's just animated... + return base_value->get_times(); + + return empty_time_set; +} + +void +WaypointRenderer::foreach_visible_waypoint(const synfigapp::ValueDesc &value_desc, + const studio::TimePlotData &time_plot_data, + std::function foreach_callback, + void* data) +{ + const Node::time_set & tset = get_times_from_valuedesc(value_desc); + if (!tset.empty()) { + const Time time_offset = get_time_offset_from_vdesc(value_desc); + const Time time_dilation = get_time_dilation_from_vdesc(value_desc); + const double time_k = time_dilation == Time::zero() ? 1.0 : 1.0/(double)time_dilation; + + for (const auto & timepoint : tset) { + Time t = (timepoint.get_time() - time_offset)*time_k; + if (time_plot_data.is_time_visible_extra(t)) { + if (foreach_callback(timepoint, t, data)) + break; + } + } + } +} + +} diff --git a/synfig-studio/src/gui/waypointrenderer.h b/synfig-studio/src/gui/waypointrenderer.h new file mode 100644 index 0000000..b7fa71e --- /dev/null +++ b/synfig-studio/src/gui/waypointrenderer.h @@ -0,0 +1,77 @@ +/* === S Y N F I G ========================================================= */ +/*! \file waypointrenderer.h +** +** $Id$ +** +** \legal +** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley +** ......... ... 2019 Rodolfo R. Gomes +** +** This package is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License as +** published by the Free Software Foundation; either version 2 of +** the License, or (at your option) any later version. +** +** This package is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** \endlegal +*/ +/* ========================================================================= */ + +/* === S T A R T =========================================================== */ + +#ifndef __SYNFIG_STUDIO_WAYPOINTRENDERER_H +#define __SYNFIG_STUDIO_WAYPOINTRENDERER_H + +/* === H E A D E R S ======================================================= */ +#include +#include + +#include + +#include "synfigapp/value_desc.h" +#include "gui/timeplotdata.h" + +/* === M A C R O S ========================================================= */ + +/* === T Y P E D E F S ===================================================== */ + +/* === C L A S S E S & S T R U C T S ======================================= */ + +namespace studio { + +class WaypointRenderer +{ +public: + static void + render_time_point_to_window( + const Cairo::RefPtr &cr, + const Gdk::Rectangle& area, + const synfig::TimePoint &tp, + bool selected, + bool hover); + + //! Callback called at every iteration of \ref foreach_visible_waypoint + //! \param tp A visible TimePoint + //! \param t The current time (including effects of layers: offset and zoom) + //! \param data Custom data passed to \ref foreach_visible_waypoint + /// \return Callback should return true to stop for-each loop + typedef bool ForeachCallback(const synfig::TimePoint &tp, const synfig::Time &t, void *data); + + static void foreach_visible_waypoint( + const synfigapp::ValueDesc &value_desc, + const studio::TimePlotData &time_plot_data, + std::function foreach_callback, + void* data = nullptr); + + static const synfig::Node::time_set & get_times_from_valuedesc(const synfigapp::ValueDesc &v); + +}; // END of class WaypointRenderer + +}; // END of namespace studio + +/* === E N D =============================================================== */ + +#endif // WAYPOINTRENDERER_H diff --git a/synfig-studio/src/gui/widgets/widget_canvastimeslider.cpp b/synfig-studio/src/gui/widgets/widget_canvastimeslider.cpp index 878f787..2367708 100644 --- a/synfig-studio/src/gui/widgets/widget_canvastimeslider.cpp +++ b/synfig-studio/src/gui/widgets/widget_canvastimeslider.cpp @@ -42,6 +42,7 @@ #include "widget_canvastimeslider.h" +#include "gui/timeplotdata.h" #endif @@ -71,6 +72,8 @@ Widget_CanvasTimeslider::Widget_CanvasTimeslider(): tooltip.add(thumb); add_events(Gdk::POINTER_MOTION_MASK | Gdk::LEAVE_NOTIFY_MASK); + + time_plot_data->set_extra_time_margin(2.0); } Widget_CanvasTimeslider::~Widget_CanvasTimeslider() @@ -105,19 +108,17 @@ Widget_CanvasTimeslider::show_tooltip(const synfig::Point &p, const synfig::Poin Cairo::RefPtr pattern; Cairo::RefPtr surface; if ( get_width() - && time_model + && time_plot_data->time_model && canvas_view && canvas_view->get_canvas() && canvas_view->get_work_area() && canvas_view->get_work_area()->get_renderer_canvas() ) { double x = p[0]; - double w = (double)get_width(); - Time lower = time_model->get_visible_lower(); - Time upper = time_model->get_visible_upper(); - if (lower < upper) { - synfig::Time time(x/w*(upper - lower) + lower); - time = time_model->round_time(time); + + if (!time_plot_data->is_invalid()) { + synfig::Time time = time_plot_data->get_t_from_pixel_coord(x); + time = time_plot_data->time_model->round_time(time); surface = canvas_view->get_work_area()->get_renderer_canvas()->get_thumb(time); pattern = canvas_view->get_work_area()->get_background_pattern(); } @@ -230,32 +231,23 @@ Widget_CanvasTimeslider::draw_background(const Cairo::RefPtr &cr { Widget_Timeslider::draw_background(cr); - if (!time_model || !canvas_view || !canvas_view->get_work_area()) return; + if (!time_plot_data->time_model || !canvas_view || !canvas_view->get_work_area()) return; Renderer_Canvas::Handle renderer_canvas = canvas_view->get_work_area()->get_renderer_canvas(); if (!renderer_canvas) return; - int w = get_width(), h = get_height(); - if (w <= 0 || h <= 0) return; - - Time lower = time_model->get_visible_lower(); - Time upper = time_model->get_visible_upper(); - Time frame_duration = time_model->get_frame_duration(); - if (lower >= upper) return; + int w = get_width(), height = get_height(); + if (w <= 0 || height <= 0) return; - double k = (double)w/(double)(upper - lower); - double extra_pixels = 1.0; - Time extra_time = extra_pixels*k; - Time lower_ex = lower - frame_duration - 2.0*extra_time; - Time upper_ex = upper + 2.0*extra_time; + Time frame_duration = time_plot_data->time_model->get_frame_duration(); + if (time_plot_data->is_invalid()) return; double top = 0.0; - double width = frame_duration*k + 1.0; - double height = (double)h; + double width = frame_duration*time_plot_data->k + 1.0; Renderer_Canvas::StatusMap status_map; renderer_canvas->get_render_status(status_map); for(Renderer_Canvas::StatusMap::const_iterator i = status_map.begin(); i != status_map.end(); ++i) { - if (i->first < lower_ex || i->first > upper_ex) continue; + if (i->first < time_plot_data->lower_ex - frame_duration|| i->first > time_plot_data->upper_ex) continue; cr->save(); switch(i->second) { case Renderer_Canvas::FS_PartiallyDone : cr->set_source_rgba(0.4, 0.4, 0.4, 1.0); break; @@ -263,7 +255,7 @@ Widget_CanvasTimeslider::draw_background(const Cairo::RefPtr &cr case Renderer_Canvas::FS_Done : cr->set_source_rgba(0.0, 0.8, 0.0, 1.0); break; default : cr->set_source_rgba(0.0, 0.0, 0.0, 0.0); break; } - cr->rectangle(((double)i->first - lower)*k, top, width, height); + cr->rectangle(time_plot_data->get_pixel_t_coord(i->first), top, width, height); cr->fill(); cr->restore(); } diff --git a/synfig-studio/src/gui/widgets/widget_curves.cpp b/synfig-studio/src/gui/widgets/widget_curves.cpp index 6c938f3..96eb3d3 100644 --- a/synfig-studio/src/gui/widgets/widget_curves.cpp +++ b/synfig-studio/src/gui/widgets/widget_curves.cpp @@ -32,8 +32,6 @@ # include #endif -#include - #include #include @@ -41,15 +39,23 @@ #include -#include #include #include #include +#include +#include -#include #include #include "widget_curves.h" +#include "gui/timeplotdata.h" + +#include "gui/waypointrenderer.h" +#include +#include + +#include "instance.h" +#include #include @@ -64,6 +70,8 @@ using namespace studio; /* === M A C R O S ========================================================= */ #define MAX_CHANNELS 15 +#define ZOOM_CHANGING_FACTOR 1.25 +#define DEFAULT_PAGE_SIZE 2.0 /* === G L O B A L S ======================================================= */ @@ -82,6 +90,7 @@ struct Widget_Curves::Channel struct Widget_Curves::CurveStruct: sigc::trackable { + std::string name; ValueDesc value_desc; std::vector channels; @@ -92,7 +101,8 @@ struct Widget_Curves::CurveStruct: sigc::trackable CurveStruct() { } - explicit CurveStruct(const ValueDesc& x) + explicit CurveStruct(const ValueDesc& x, std::string name) + : name(name) { init(x); } bool init(const ValueDesc& x) { @@ -155,101 +165,386 @@ struct Widget_Curves::CurveStruct: sigc::trackable i->values.clear(); } - Real get_value(int channel, Real time, Real tolerance) { + Real get_value(size_t channel, Real time, Real tolerance) { // First check to see if we have a value // that is "close enough" to the time // we are looking for std::map::iterator i = channels[channel].values.lower_bound(time); if (i != channels[channel].values.end() && i->first - time <= tolerance) - return -i->second; + return i->second; // Since that didn't work, we now need // to go ahead and figure out what the // actual value is at that time. ValueBase value(value_desc.get_value(time)); - Type &type(value.get_type()); + std::vector channel_values; + bool ok = get_value_base_channel_values(value, channel_values); + if (!ok) + return Real(0.0); + + for (size_t c = 0; c < channel_values.size(); c++) { + channels[c].values[time] = channel_values[c]; + } + + return channels[channel].values[time]; + } + + static bool get_value_base_channel_values(const ValueBase &value_base, std::vector& channels) { + channels.clear(); + Type &type(value_base.get_type()); if (type == type_real) { - channels[0].values[time] = value.get(Real()); + channels.push_back(value_base.get(Real())); } else if (type == type_time) { - channels[0].values[time] = value.get(Time()); + channels.push_back(value_base.get(Time())); } else if (type == type_integer) { - channels[0].values[time] = value.get(int()); + channels.push_back(value_base.get(int())); } else if (type == type_bool) { - channels[0].values[time] = value.get(bool()); + channels.push_back(value_base.get(bool())); } else if (type == type_angle) { - channels[0].values[time] = Angle::rad(value.get(Angle())).get(); + channels.push_back(Angle::rad(value_base.get(Angle())).get()); } else if (type == type_color) { - channels[0].values[time] = value.get(Color()).get_r(); - channels[1].values[time] = value.get(Color()).get_g(); - channels[2].values[time] = value.get(Color()).get_b(); - channels[3].values[time] = value.get(Color()).get_a(); + const Color & color = value_base.get(Color()); + channels.push_back(color.get_r()); + channels.push_back(color.get_g()); + channels.push_back(color.get_b()); + channels.push_back(color.get_a()); } else if (type == type_vector) { - channels[0].values[time] = value.get(Vector())[0]; - channels[1].values[time] = value.get(Vector())[1]; + const Vector& vector = value_base.get(Vector()); + channels.push_back(vector[0]); + channels.push_back(vector[1]); } else if (type == type_bline_point) { - channels[0].values[time] = value.get(BLinePoint()).get_vertex()[0]; - channels[1].values[time] = value.get(BLinePoint()).get_vertex()[1]; - channels[2].values[time] = value.get(BLinePoint()).get_width(); - channels[3].values[time] = value.get(BLinePoint()).get_origin(); - channels[4].values[time] = value.get(BLinePoint()).get_split_tangent_both(); - channels[5].values[time] = value.get(BLinePoint()).get_tangent1()[0]; - channels[6].values[time] = value.get(BLinePoint()).get_tangent1()[1]; - channels[7].values[time] = value.get(BLinePoint()).get_tangent2()[0]; - channels[8].values[time] = value.get(BLinePoint()).get_tangent2()[1]; - channels[9].values[time] = value.get(BLinePoint()).get_split_tangent_radius(); - channels[10].values[time]= value.get(BLinePoint()).get_split_tangent_angle(); + const BLinePoint &bline_point = value_base.get(BLinePoint()); + channels.push_back(bline_point.get_vertex()[0]); + channels.push_back(bline_point.get_vertex()[1]); + channels.push_back(bline_point.get_width()); + channels.push_back(bline_point.get_origin()); + channels.push_back(bline_point.get_split_tangent_both()); + channels.push_back(bline_point.get_tangent1()[0]); + channels.push_back(bline_point.get_tangent1()[1]); + channels.push_back(bline_point.get_tangent2()[0]); + channels.push_back(bline_point.get_tangent2()[1]); + channels.push_back(bline_point.get_split_tangent_radius()); + channels.push_back(bline_point.get_split_tangent_angle()); } else if (type == type_width_point) { - channels[0].values[time] = value.get(WidthPoint()).get_position(); - channels[1].values[time] = value.get(WidthPoint()).get_width(); + const WidthPoint &width_point = value_base.get(WidthPoint()); + channels.push_back(width_point.get_position()); + channels.push_back(width_point.get_width()); } else if (type == type_dash_item) { - channels[0].values[time] = value.get(DashItem()).get_offset(); - channels[1].values[time] = value.get(DashItem()).get_length(); + const DashItem &dash_item = value_base.get(DashItem()); + channels.push_back(dash_item.get_offset()); + channels.push_back(dash_item.get_length()); } else { - return Real(0.0); + return false; + } + + return true; + } + + static bool set_value_base_channel_value(ValueBase& value_base, size_t channel_idx, Real v) + { + Type& type = value_base.get_type(); + if (type == type_real) { + if (channel_idx > 0) { + synfig::error("Invalid index for Real curve channel: %d", channel_idx); + return false; + } else { + value_base.set(v); + } + } else + if (type == type_time) { + if (channel_idx > 0) { + synfig::error("Invalid index for Time curve channel: %d", channel_idx); + return false; + } else { + value_base.set(Time(v)); + } + } else + if (type == type_integer) { + if (channel_idx > 0) { + synfig::error("Invalid index for Integer curve channel: %d", channel_idx); + return false; + } else { + value_base.set((int)v); + } + } else + if (type == type_bool) { + if (channel_idx > 0) { + synfig::error("Invalid index for Bool curve channel: %d", channel_idx); + return false; + } else { + value_base.set(v > 0.5); + } + } else + if (type == type_angle) { + if (channel_idx > 0) { + synfig::error("Invalid index for Real curve channel: %d", channel_idx); + return false; + } else { + value_base.set(Angle::rad(v)); + } + } else + if (type == type_color) { + v = clamp(v, 0.0, 1.0); + auto color = value_base.get(Color()); + switch (channel_idx) { + case 0: + color.set_r(v); + break; + case 1: + color.set_g(v); + break; + case 2: + color.set_b(v); + break; + case 3: + color.set_a(v); + break; + default: + synfig::error("Invalid index for Color curve channel: %d", channel_idx); + return false; + } + + value_base.set(color); + } else + if (type == type_vector) { + if (channel_idx > 1) { + synfig::error("Invalid index for Vector curve channel: %d", channel_idx); + return false; + } else { + auto vector = value_base.get(Vector()); + vector[channel_idx] = v; + value_base.set(vector); + } + } else + if (type == type_bline_point) { + BLinePoint bline_point = value_base.get(BLinePoint()); + switch (channel_idx) { + case 0: { + Vector vertex = bline_point.get_vertex(); + vertex[0] = v; + bline_point.set_vertex(vertex); + break; + } + case 1: { + Vector vertex = bline_point.get_vertex(); + vertex[1] = v; + bline_point.set_vertex(vertex); + break; + } + case 2: + bline_point.set_width( v < 0 ? 0 : v); + break; + case 3: + bline_point.set_origin(v); + break; + case 4: + bline_point.set_split_tangent_both(v > 0.5); + break; + case 5: { + Vector tangent = bline_point.get_tangent1(); + tangent[0] = v; + bline_point.set_tangent1(tangent); + break; + } + case 6: { + Vector tangent = bline_point.get_tangent1(); + tangent[1] = v; + bline_point.set_tangent1(tangent); + break; + } + case 7: { + Vector tangent = bline_point.get_tangent2(); + tangent[0] = v; + bline_point.set_tangent2(tangent); + break; + } + case 8: { + Vector tangent = bline_point.get_tangent2(); + tangent[1] = v; + bline_point.set_tangent2(tangent); + break; + } + case 9: + bline_point.set_split_tangent_radius(v > 0.5); + break; + case 10: + bline_point.set_split_tangent_angle(v > 0.5); + break; + default: + synfig::error("Invalid index for BLinePoint curve channel: %d", channel_idx); + return false; + } + value_base.set(bline_point); + } else + if (type == type_width_point) { + WidthPoint width_point = value_base.get(WidthPoint()); + if (channel_idx == 0) { + width_point.set_position(v); + } else if (channel_idx == 1) { + if (v < 0) + v = 0; + width_point.set_width(v); + } else { + synfig::error("Invalid index for WidthPoint curve channel: %d", channel_idx); + return false; + } + value_base.set(width_point); + } else + if (type == type_dash_item) { + DashItem dash_item = value_base.get(DashItem()); + if (channel_idx == 0) { + dash_item.set_offset(v); + } else if (channel_idx == 1) { + if (v < 0) + v = 0; + dash_item.set_length(v); + } else { + synfig::error("Invalid index for DashItem curve channel: %d", channel_idx); + return false; + } + value_base.set(dash_item); + } + return true; + } + +}; + +struct Tooltip +{ + const int outline_width = 2; + const Color bg_color{0.9f, 0.9f, 0, 0.8f}; + const Color fg_color{0.5f, 0.5f, 0.5f, 1}; + const Color text_color{0.0f, 0.0f, 0.0f}; + + int popup_margin = 0; + int padding = 5; + + Gtk::Widget *widget; + + std::string text; + + Tooltip(Gtk::Widget *widget_) + : widget(widget_) + {} + + void draw(const Cairo::RefPtr& cr, int pos_x, int pos_y) { + + guint cursor_w, cursor_h; + cursor_w = cursor_h = widget->get_display()->get_default_cursor_size(); + int info_pos_x = pos_x; + info_pos_x += cursor_w + popup_margin + padding; + int info_pos_y = pos_y + padding; +// info_pos_y += cursor_h; + + Glib::RefPtr layout(Pango::Layout::create(widget->get_pango_context())); + layout->set_markup(text); + Pango::Rectangle text_rect = layout->get_pixel_ink_extents(); + if (info_pos_y + popup_margin + text_rect.get_height() > widget->get_height()) + info_pos_y = widget->get_height() - text_rect.get_height() - popup_margin - padding; + else if (info_pos_y < popup_margin + padding) + info_pos_y = popup_margin + padding; + + if (info_pos_x + padding + popup_margin + text_rect.get_width() > widget->get_width()) { + info_pos_x = std::min(pos_x, widget->get_width()); + info_pos_x -= text_rect.get_width() + popup_margin + padding; + } else if (info_pos_x < popup_margin) { + info_pos_x = std::max(0, pos_x); + info_pos_x += popup_margin + padding; } - return -channels[channel].values[time]; + cr->set_source_rgba(bg_color.get_r(), bg_color.get_g(), bg_color.get_b(), bg_color.get_alpha()); + cr->rectangle(info_pos_x - outline_width - padding, info_pos_y - outline_width - padding, text_rect.get_width() + 2*outline_width + 2*padding, text_rect.get_height() + 2*outline_width + 2*padding); + cr->fill_preserve(); + cr->set_source_rgba(fg_color.get_r(), fg_color.get_g(), fg_color.get_b(), fg_color.get_alpha()); + cr->set_line_width(1); + cr->stroke(); + + cr->move_to(info_pos_x, info_pos_y); + cr->set_source_rgb(text_color.get_r(), text_color.get_g(), text_color.get_b()); + layout->show_in_cairo_context(cr); } }; /* === M E T H O D S ======================================================= */ +void Widget_Curves::on_waypoint_clicked(const Widget_Curves::ChannelPoint& cp, unsigned int button, Gdk::Point) +{ + std::set > waypoint_set; + synfig::waypoint_collect(waypoint_set, cp.time_point.get_time(), cp.curve_it->value_desc.get_value_node()); + signal_waypoint_clicked().emit(cp.curve_it->value_desc, waypoint_set, button); +} + +void Widget_Curves::on_waypoint_double_clicked(const Widget_Curves::ChannelPoint& cp, unsigned int button, Gdk::Point) +{ + std::set > waypoint_set; + synfig::waypoint_collect(waypoint_set, cp.time_point.get_time(), cp.curve_it->value_desc.get_value_node()); + signal_waypoint_double_clicked().emit(cp.curve_it->value_desc, waypoint_set, button); +} + Widget_Curves::Widget_Curves(): - range_adjustment(Gtk::Adjustment::create(-1.0, -2.0, 2.0, 0.1, 0.1, 2)) + channel_point_sd(*this), + range_adjustment(Gtk::Adjustment::create(-1.0, -2.0, 2.0, 0.1, 0.1, DEFAULT_PAGE_SIZE)), + waypoint_edge_length(16) { set_size_request(64, 64); - range_adjustment->signal_changed().connect( - sigc::mem_fun(*this, &Widget_Curves::queue_draw) ); - range_adjustment->signal_value_changed().connect( - sigc::mem_fun(*this, &Widget_Curves::queue_draw) ); + add_events(Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::SCROLL_MASK | Gdk::POINTER_MOTION_MASK | Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK); - add_events(Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::SCROLL_MASK); + set_can_focus(true); + + time_plot_data = new TimePlotData(*this, range_adjustment); + time_plot_data->set_extra_time_margin(16/2); + + channel_point_sd.set_pan_enabled(true); + channel_point_sd.set_zoom_enabled(true); + channel_point_sd.set_scroll_enabled(true); + channel_point_sd.set_canvas_interface(canvas_interface); + channel_point_sd.signal_drag_canceled().connect([&]() { + overlapped_waypoints.clear(); + }); + channel_point_sd.signal_drag_finished().connect([&]() { +// overlapped_waypoints.clear(); + }); + channel_point_sd.signal_redraw_needed().connect(sigc::mem_fun(*this, &Gtk::Widget::queue_draw)); + channel_point_sd.signal_focus_requested().connect(sigc::mem_fun(*this, &Gtk::Widget::grab_focus)); + channel_point_sd.signal_selection_changed().connect([=](){ + overlapped_waypoints.clear(); + queue_draw(); + }); + channel_point_sd.signal_zoom_in_requested().connect(sigc::mem_fun(*this, &Widget_Curves::zoom_in)); + channel_point_sd.signal_zoom_out_requested().connect(sigc::mem_fun(*this, &Widget_Curves::zoom_out)); + channel_point_sd.signal_scroll_up_requested().connect(sigc::mem_fun(*this, &Widget_Curves::scroll_up)); + channel_point_sd.signal_scroll_down_requested().connect(sigc::mem_fun(*this, &Widget_Curves::scroll_down)); + channel_point_sd.signal_panning_requested().connect(sigc::mem_fun(*this, &Widget_Curves::pan)); + channel_point_sd.signal_item_clicked().connect(sigc::mem_fun(*this, &Widget_Curves::on_waypoint_clicked)); + channel_point_sd.signal_item_double_clicked().connect(sigc::mem_fun(*this, &Widget_Curves::on_waypoint_double_clicked)); } Widget_Curves::~Widget_Curves() { clear(); set_time_model(etl::handle()); + delete time_plot_data; +} + +const etl::handle& +Widget_Curves::get_time_model() const +{ + return time_plot_data->time_model; } void Widget_Curves::set_time_model(const etl::handle &x) { - if (time_model == x) return; - time_changed.disconnect(); - time_model = x; - if (time_model) - time_changed = time_model->signal_time_changed().connect( - sigc::mem_fun(*this, &Widget_Curves::queue_draw) ); + time_plot_data->set_time_model(x); } void @@ -259,6 +554,7 @@ Widget_Curves::clear() { value_desc_changed.pop_back(); } curve_list.clear(); + channel_point_sd.clear(); } void @@ -266,15 +562,88 @@ Widget_Curves::refresh() { for(std::list::iterator i = curve_list.begin(); i != curve_list.end(); ++i) i->clear_all_values(); + channel_point_sd.refresh(); queue_draw(); } +void Widget_Curves::zoom_in() +{ + set_zoom(get_zoom() * ZOOM_CHANGING_FACTOR); +} + +void Widget_Curves::zoom_out() +{ + set_zoom(get_zoom() / ZOOM_CHANGING_FACTOR); +} + +void Widget_Curves::zoom_100() +{ + set_zoom(1.0); +} + +void Widget_Curves::set_zoom(double new_zoom_factor) +{ + int x, y; + get_pointer(x, y); + double perc_y = y/(get_height()+0.0); + double y_value = perc_y * range_adjustment->get_page_size() + range_adjustment->get_value(); + double new_range_page_size = DEFAULT_PAGE_SIZE / new_zoom_factor; + double new_range_value = y_value - perc_y * new_range_page_size; + ConfigureAdjustment(range_adjustment) + .set_page_size(new_range_page_size) + .set_value(new_range_value) + .finish(); +} + +double Widget_Curves::get_zoom() const +{ + return DEFAULT_PAGE_SIZE / range_adjustment->get_page_size(); +} + +void Widget_Curves::scroll_up() +{ + ConfigureAdjustment(range_adjustment) + .set_value(range_adjustment->get_value() - range_adjustment->get_step_increment()) + .finish(); +} + +void Widget_Curves::scroll_down() +{ + ConfigureAdjustment(range_adjustment) + .set_value(range_adjustment->get_value() + range_adjustment->get_step_increment()) + .finish(); +} + +void Widget_Curves::pan(int dx, int dy, int /*total_dx*/, int /*total_dy*/) +{ + Time dt(-dx*time_plot_data->dt); + time_plot_data->time_model->move_by(dt); + + double real_dy = (range_adjustment->get_page_size()*dy)/get_height(); + + ConfigureAdjustment(range_adjustment) + .set_value(range_adjustment->get_value() - real_dy) + .finish(); +} + +void Widget_Curves::select_all_points() +{ + channel_point_sd.select_all_items(); +} + void -Widget_Curves::set_value_descs(const std::list &value_descs) +Widget_Curves::set_value_descs(etl::handle canvas_interface_, const std::list< std::pair > &data) { - curve_list.clear(); + if (canvas_interface_ != canvas_interface) { + canvas_interface = canvas_interface_; + channel_point_sd.set_canvas_interface(canvas_interface_); + } + clear(); CurveStruct curve_struct; - for(std::list::const_iterator i = value_descs.begin(); i != value_descs.end(); ++i) { + for(auto it = data.begin(); it != data.end(); ++it) { + const ValueDesc *i = &it->second; + + curve_struct.name = it->first; curve_struct.init(*i); if (curve_struct.channels.empty()) continue; @@ -300,44 +669,23 @@ Widget_Curves::set_value_descs(const std::list &value_descs) bool Widget_Curves::on_event(GdkEvent *event) { - switch(event->type) { - case GDK_SCROLL: { - switch(event->scroll.direction) { - case GDK_SCROLL_UP: - case GDK_SCROLL_RIGHT: { - if (event->scroll.state & GDK_CONTROL_MASK) { - // Ctrl+scroll , perform zoom in - ConfigureAdjustment(range_adjustment) - .set_page_size(range_adjustment->get_page_size()/1.25) - .finish(); - } else { - // Scroll up - ConfigureAdjustment(range_adjustment) - .set_value(range_adjustment->get_value() - range_adjustment->get_step_increment()) - .finish(); - } - return true; - } - case GDK_SCROLL_DOWN: - case GDK_SCROLL_LEFT: { - if (event->scroll.state & GDK_CONTROL_MASK) { - // Ctrl+scroll , perform zoom out - ConfigureAdjustment(range_adjustment) - .set_page_size(range_adjustment->get_page_size()*1.25) - .finish(); - } else { - // Scroll down - ConfigureAdjustment(range_adjustment) - .set_value(range_adjustment->get_value() + range_adjustment->get_step_increment()) - .finish(); - } - return true; - } - default: - break; + if (channel_point_sd.process_event(event)) + return true; + + switch (event->type) { + case GDK_KEY_PRESS: + switch (event->key.keyval) { + case GDK_KEY_Delete: + delete_selected(); + return true; + default: + break; + } + case GDK_2BUTTON_PRESS: + if (event->button.button == 1) { + add_waypoint_to(event->button.x, event->button.y); } break; - } default: break; } @@ -355,24 +703,17 @@ Widget_Curves::on_draw(const Cairo::RefPtr &cr) get_style_context()->render_background(cr, 0, 0, w, h); - if (!time_model || !curve_list.size()) + if (!time_plot_data->time_model || !curve_list.size()) return true; - Time time = time_model->get_time(); - Time lower = time_model->get_visible_lower(); - Time upper = time_model->get_visible_upper(); - double k = (double)w/(double)(upper - lower); - Time dt(1.0/k); - - Real range_lower = range_adjustment->get_value(); - Real range_upper = range_lower + range_adjustment->get_page_size(); - double range_k = (double)h/(double)(range_upper - range_lower); + if (time_plot_data->is_invalid()) + return true; cr->save(); // Draw zero mark cr->set_source_rgb(0.31, 0.31, 0.31); - cr->rectangle(0, etl::round_to_int((0.0 - range_lower)*range_k), w, 0); + cr->rectangle(0, time_plot_data->get_pixel_y_coord(0.0), w, 0); cr->stroke(); // This try to find a valid canvas to show the keyframes of those @@ -388,8 +729,8 @@ Widget_Curves::on_draw(const Cairo::RefPtr &cr) for(KeyframeList::const_iterator i = canvas->keyframe_list().begin(); i != canvas->keyframe_list().end(); ++i) { if (!i->get_time().is_valid()) continue; - int x = (i->get_time() - lower)*k; - if (i->get_time() >= lower && i->get_time() <= upper) { + if (time_plot_data->is_time_visible(i->get_time())) { + int x = time_plot_data->get_pixel_t_coord(i->get_time()); cr->set_source_rgb(0.63, 0.5, 0.5); cr->rectangle(x, 0, 1, h); cr->fill(); @@ -399,68 +740,582 @@ Widget_Curves::on_draw(const Cairo::RefPtr &cr) // Draw current time cr->set_source_rgb(0, 0, 1); - cr->rectangle(etl::round_to_int((double)(time - lower)*k), 0, 0, h); + cr->rectangle(time_plot_data->get_pixel_t_coord(time_plot_data->time), 0, 0, h); cr->stroke(); // reserve arrays for maximum number of channels - int max_channels = 0; + size_t max_channels = 0; for(std::list::iterator i = curve_list.begin(); i != curve_list.end(); ++i) - max_channels = std::max(max_channels, (int)i->channels.size()); + max_channels = std::max(max_channels, i->channels.size()); std::vector< std::vector > points(max_channels); Real range_max = -100000000.0; Real range_min = 100000000.0; + // draw overlapped waypoints + cr->set_line_width(.4); + for (auto it : overlapped_waypoints) { + const Waypoint &waypoint = it.first; + const auto &curve_it = it.second; + const size_t num_channels = curve_it->channels.size(); + + const int x = time_plot_data->get_pixel_t_coord(waypoint.get_time()); + + std::vector channel_values; + CurveStruct::get_value_base_channel_values(waypoint.get_value(), channel_values); + for (size_t c = 0; c < num_channels; c++) { + Real value = curve_it->get_value(c, waypoint.get_time(), time_plot_data->dt); + int y = time_plot_data->get_pixel_y_coord(value); + + Real old_value = channel_values[c]; + int old_y = time_plot_data->get_pixel_y_coord(old_value); + + range_max = std::max(range_max, old_value); + range_min = std::min(range_min, old_value); + + Gdk::Cairo::set_source_color(cr, curve_it->channels[c].color); + cr->move_to(x, y); + cr->line_to(x, old_y); + cr->stroke(); + cr->arc(x, old_y, waypoint_edge_length / 5, 0, 6.28); + cr->fill_preserve(); + cr->stroke(); + } + } + // Draw curves for the valuenodes stored in the curve list - for(std::list::iterator i = curve_list.begin(); i != curve_list.end(); ++i) { - int channels = (int)i->channels.size(); - if (channels > (int)points.size()) + for(std::list::iterator curve_it = curve_list.begin(); curve_it != curve_list.end(); ++curve_it) { + size_t channels = curve_it->channels.size(); + if (channels > points.size()) points.resize(channels); - for(int c = 0; c < channels; ++c) { + for(size_t c = 0; c < channels; ++c) { points[c].clear(); points[c].reserve(w); } - Time t = lower; - for(int j = 0; j < w; ++j, t += dt) { - for(int c = 0; c < channels; ++c) { - Real x = i->get_value(c, t, dt); - range_max = std::max(range_max, x); - range_min = std::min(range_min, x); - points[c].push_back( Gdk::Point(j, etl::round_to_int((x - range_lower)*range_k)) ); + Time t = time_plot_data->lower; + for(int j = 0; j < w; ++j, t += time_plot_data->dt) { + for(size_t c = 0; c < channels; ++c) { + Real y = curve_it->get_value(c, t, time_plot_data->dt); + range_max = std::max(range_max, y); + range_min = std::min(range_min, y); + points[c].push_back( Gdk::Point(j, time_plot_data->get_pixel_y_coord(y)) ); } } + // Get last time point for this graph curve + Time last_timepoint; + const Node::time_set & tset = WaypointRenderer::get_times_from_valuedesc(curve_it->value_desc); + for (const auto & timepoint : tset) { + if (timepoint.get_time() > last_timepoint) + last_timepoint = timepoint.get_time(); + } + int last_timepoint_pixel = time_plot_data->get_pixel_t_coord(last_timepoint); + // Draw the graph curves with 0.5 width cr->set_line_width(0.5); - for(int c = 0; c < channels; ++c) { + const std::vector dashes4 = {4}; + const std::vector no_dashes; + for(size_t c = 0; c < channels; ++c) { // Draw the curve std::vector &p = points[c]; - for(std::vector::iterator j = p.begin(); j != p.end(); ++j) { - if (j == p.begin()) - cr->move_to(j->get_x(), j->get_y()); + std::vector::iterator p_it; + for(p_it = p.begin(); p_it != p.end(); ++p_it) { + if (p_it == p.begin()) + cr->move_to(p_it->get_x(), p_it->get_y()); else - cr->line_to(j->get_x(), j->get_y()); + cr->line_to(p_it->get_x(), p_it->get_y()); + if (p_it->get_x() >= last_timepoint_pixel) + break; } - Gdk::Cairo::set_source_color(cr, i->channels[c].color); + Gdk::Cairo::set_source_color(cr, curve_it->channels[c].color); cr->stroke(); + // Draw the remaining curve + if (p_it != p.end()) { + for(; p_it != p.end(); ++p_it) { + cr->line_to(p_it->get_x(), p_it->get_y()); + } + cr->set_dash(dashes4, 0); + cr->stroke(); + + cr->set_dash(no_dashes, 0); + } + Glib::RefPtr layout(Pango::Layout::create(get_pango_context())); - layout->set_text(i->channels[c].name); + layout->set_text(curve_it->channels[c].name); cr->move_to(1, points[c][0].get_y() + 1); layout->show_in_cairo_context(cr); } + + // Draw waypoints + bool is_draggable = curve_it->value_desc.is_animated() || curve_it->value_desc.parent_is_linkable_value_node(); + if (!is_draggable) { + cr->push_group(); + } + WaypointRenderer::foreach_visible_waypoint(curve_it->value_desc, *time_plot_data, + [&](const synfig::TimePoint &tp, const synfig::Time &t, void *_data) -> bool + { + int px = time_plot_data->get_pixel_t_coord(t); + Gdk::Rectangle area( + 0 - waypoint_edge_length/2 + 1 + px, + 0, //0 - waypoint_edge_length/2 + 1 + py, + waypoint_edge_length - 2, + waypoint_edge_length - 2); + const auto & hovered_point = channel_point_sd.get_hovered_item(); + bool hover = hovered_point.is_valid() && tp == hovered_point.time_point && hovered_point.curve_it == curve_it; + for (size_t c = 0; c < channels; ++c) { + Real y = curve_it->get_value(c, t, time_plot_data->dt); + int py = time_plot_data->get_pixel_y_coord(y); + area.set_y(0 - waypoint_edge_length/2 + 1 + py); + + bool selected = channel_point_sd.is_selected(ChannelPoint(curve_it, tp, c)); + WaypointRenderer::render_time_point_to_window(cr, area, tp, selected, hover); + } + return false; + }); + if (!is_draggable) { + cr->pop_group_to_source(); + cr->paint_with_alpha(0.5); + } } - if (!curve_list.empty() && range_min < range_max) + // Draw selection rectangle + if (channel_point_sd.get_state() == ChannelPointSD::State::POINTER_SELECTING) { + // set up a dashed solid-color stroke + static const std::vectordashed3 = {5.0}; + cr->set_dash(dashed3, 0); + + int x0, y0; + int x1, y1; + channel_point_sd.get_initial_tracking_point(x0, y0); + get_pointer(x1, y1); + + cr->rectangle(x0, y0, x1 - x0, y1 - y0); + Gdk::RGBA color = get_style_context()->get_color(); + cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue()); + cr->stroke(); + } + + // Draw info about hovered item + if (channel_point_sd.get_hovered_item().is_valid() || channel_point_sd.get_state() == channel_point_sd.POINTER_DRAGGING) { + const ChannelPoint* inspected_item = &channel_point_sd.get_hovered_item(); + if (!inspected_item->is_valid()) + inspected_item = channel_point_sd.get_active_item(); + + float fps = canvas_interface->get_canvas()->rend_desc().get_frame_rate(); + + char buf[512]; + if (channel_point_sd.get_state() != channel_point_sd.POINTER_DRAGGING) { + snprintf(buf, 511, "%s:%s\nTime: %lfs (%if)\nValue: %lf", + inspected_item->curve_it->name.c_str(), + inspected_item->curve_it->channels[inspected_item->channel_idx].name.c_str(), + Real(inspected_item->time_point.get_time()), + int(std::trunc(inspected_item->time_point.get_time()*fps)), + inspected_item->get_value(time_plot_data->dt) + ); + } else { + int x0, y0; + channel_point_sd.get_initial_tracking_point(x0, y0); + Time t0 = time_plot_data->get_t_from_pixel_coord(x0).round(fps); + Real value0 = time_plot_data->get_y_from_pixel_coord(y0); + snprintf(buf, 511, _("%s:%s\nTime: %lfs (%if) ( \u0394 %if )\nValue: %lf ( \u0394 %lf )"), + inspected_item->curve_it->name.c_str(), + inspected_item->curve_it->channels[inspected_item->channel_idx].name.c_str(), + Real(inspected_item->time_point.get_time()), + int(std::trunc(inspected_item->time_point.get_time()*fps)), + int(std::trunc((inspected_item->time_point.get_time() - t0)*fps)), + inspected_item->get_value(time_plot_data->dt), + inspected_item->get_value(time_plot_data->dt) - value0 + ); + } + std::string text(buf); + + int item_pos_x = time_plot_data->get_pixel_t_coord(inspected_item->time_point.get_time()); + int item_pos_y = time_plot_data->get_pixel_y_coord(inspected_item->get_value(time_plot_data->dt)); + + Tooltip tooltip(this); + tooltip.text = text; + tooltip.popup_margin = waypoint_edge_length; + tooltip.draw(cr, item_pos_x, item_pos_y); + } + + if (channel_point_sd.get_state() != channel_point_sd.POINTER_DRAGGING && channel_point_sd.get_state() != channel_point_sd.POINTER_PANNING && !curve_list.empty() && range_min < range_max) ConfigureAdjustment(range_adjustment) - .set_lower(range_min - 0.5*range_adjustment->get_page_size()) - .set_upper(range_max + 0.5*range_adjustment->get_page_size()) + .set_lower(-range_max - 0.5*range_adjustment->get_page_size()) + .set_upper(-range_min + 0.5*range_adjustment->get_page_size()) .set_step_increment(range_adjustment->get_page_size()*20.0/(double)h) // 20 pixels .finish(); cr->restore(); return true; } + +void +Widget_Curves::delete_selected() +{ + for (ChannelPoint *cp : channel_point_sd.get_selected_items()) { + std::set > waypoint_set; + synfig::waypoint_collect(waypoint_set, cp->time_point.get_time(), cp->curve_it->value_desc.get_value_node()); + for (const Waypoint &waypoint : waypoint_set) { + canvas_interface->waypoint_remove(cp->curve_it->value_desc, waypoint); + } + } +} + +bool +Widget_Curves::add_waypoint_to(int point_x, int point_y) +{ + // Search for nearest curve within a certain limited margin of tolerance + int tolerance = 5; + Time time = time_plot_data->get_t_from_pixel_coord(point_x); + CurveStruct * clicked_curve = nullptr; + + for (auto &curve : curve_list) { + for (size_t cidx = 0; cidx < curve.channels.size(); cidx++) { + auto pixel = time_plot_data->get_pixel_y_coord(curve.get_value(cidx, time, time_plot_data->dt)); + int diff = std::abs(point_y - pixel); + if ( diff < tolerance) { + clicked_curve = &curve; + tolerance = diff; + } + } + } + + if (!clicked_curve) + return false; + + // Add waypoint + + Action::Handle action(Action::create("WaypointAdd")); + + assert(action); + if(!action) + return false; + + Waypoint waypoint(clicked_curve->value_desc.get_value(time), time); + + action->set_param("canvas", canvas_interface->get_canvas()); + action->set_param("widget.canvas_interface", canvas_interface); + action->set_param("value_node", clicked_curve->value_desc.get_value_node()); + action->set_param("time", time); + action->set_param("waypoint", waypoint); + + if(!canvas_interface->get_instance()->perform_action(action)) + canvas_interface->get_ui_interface()->error(_("Action Failed.")); + return true; +} + +Widget_Curves::ChannelPoint::ChannelPoint() +{ + invalidate(); +} + +Widget_Curves::ChannelPoint::ChannelPoint(std::list::iterator& curve_it, const TimePoint time_point, size_t channel_idx) : + curve_it(curve_it), time_point(time_point), channel_idx(channel_idx) +{ +} + +void Widget_Curves::ChannelPoint::invalidate() +{ + channel_idx = MAX_CHANNELS; +} + +bool Widget_Curves::ChannelPoint::is_valid() const +{ + return channel_idx < MAX_CHANNELS; +} + +bool Widget_Curves::ChannelPoint::is_draggable() const +{ + const ValueDesc &value_desc(curve_it->value_desc); + return value_desc.is_animated() || value_desc.parent_is_linkable_value_node(); +} + +bool Widget_Curves::ChannelPoint::operator ==(const Widget_Curves::ChannelPoint& b) const +{ + return curve_it == b.curve_it && time_point == b.time_point && channel_idx == b.channel_idx; +} + +Real Widget_Curves::ChannelPoint::get_value(Real time_tolerance) const +{ + return curve_it->get_value(channel_idx, time_point.get_time(), time_tolerance); +} + +Widget_Curves::ChannelPointSD::ChannelPointSD(Widget_Curves& widget) + : SelectDragHelper(_("Change animation curve")), + widget(widget) +{ +} + +void Widget_Curves::ChannelPointSD::get_item_position(const Widget_Curves::ChannelPoint& item, Gdk::Point& position) +{ + const Time &time = item.time_point.get_time(); + Real value = item.get_value(widget.time_plot_data->dt); + position.set_x(widget.time_plot_data->get_pixel_t_coord(time)); + position.set_y(widget.time_plot_data->get_pixel_y_coord(value)); +} + +bool Widget_Curves::ChannelPointSD::find_item_at_position(int pos_x, int pos_y, Widget_Curves::ChannelPoint& cp) +{ + cp.invalidate(); + for(auto curve_it = widget.curve_list.begin(); curve_it != widget.curve_list.end(); ++curve_it) { + size_t channels = curve_it->channels.size(); + + WaypointRenderer::foreach_visible_waypoint(curve_it->value_desc, *widget.time_plot_data, + [&](const synfig::TimePoint &tp, const synfig::Time &t, void *data) -> bool + { + int px = widget.time_plot_data->get_pixel_t_coord(t); + for (size_t c = 0; c < channels; ++c) { + Real y = curve_it->get_value(c, t, widget.time_plot_data->dt); + int py = widget.time_plot_data->get_pixel_y_coord(y); + + if (pos_x > px - widget.waypoint_edge_length/2 && pos_x <= px + widget.waypoint_edge_length/2) { + if (pos_y > py - widget.waypoint_edge_length/2 && pos_y <= py + widget.waypoint_edge_length/2) { + cp.curve_it = curve_it; + cp.time_point = tp; + cp.channel_idx = c; + return true; + } + } + } + return false; + }); + + if (cp.is_valid()) + return true; + } + return false; +} + +bool Widget_Curves::ChannelPointSD::find_items_in_rect(Gdk::Rectangle rect, std::vector& list) +{ + list.clear(); + + int x0 = rect.get_x(); + int x1 = rect.get_x() + rect.get_width(); + if (x0 > x1) + std::swap(x0, x1); + int y0 = rect.get_y(); + int y1 = rect.get_y() + rect.get_height(); + if (y0 > y1) + std::swap(y0, y1); + + for(auto curve_it = widget.curve_list.begin(); curve_it != widget.curve_list.end(); ++curve_it) { + size_t channels = curve_it->channels.size(); + + WaypointRenderer::foreach_visible_waypoint(curve_it->value_desc, *widget.time_plot_data, + [&](const synfig::TimePoint &tp, const synfig::Time &t, void *data) -> bool + { + int px = widget.time_plot_data->get_pixel_t_coord(t); + for (size_t c = 0; c < channels; ++c) { + Real y = curve_it->get_value(c, t, widget.time_plot_data->dt); + int py = widget.time_plot_data->get_pixel_y_coord(y); + + if (x0 < px + widget.waypoint_edge_length/2 && x1 >= px - widget.waypoint_edge_length/2) { + if (y0 < py + widget.waypoint_edge_length/2 && y1 >= py - widget.waypoint_edge_length/2) { + list.push_back(ChannelPoint(curve_it, tp, c)); + } + } + } + return false; + }); + } + return list.size() > 0; +} + +void Widget_Curves::ChannelPointSD::get_all_items(std::vector& items) +{ + for (std::list::iterator curve_it = widget.curve_list.begin(); curve_it != widget.curve_list.end(); ++curve_it) { + const auto &time_set = WaypointRenderer::get_times_from_valuedesc(curve_it->value_desc); + for (size_t channel_idx = 0; channel_idx < curve_it->channels.size(); channel_idx++) { + for (const TimePoint &time : time_set) { + items.push_back(ChannelPoint(curve_it, time, channel_idx)); + } + } + } +} + +void Widget_Curves::ChannelPointSD::delta_drag(int total_dx, int total_dy, bool by_keys) +{ + int dx = 0, dy = 0; + if (total_dx == 0 && total_dy == 0) + return; + + if (by_keys) { + // snap to frames + dx = total_dx * widget.time_plot_data->k/widget.canvas_interface->get_canvas()->rend_desc().get_frame_rate(); + dy = total_dy; + } else { + Gdk::Point current_pos; + get_item_position(*get_active_item(), current_pos); + int x0, y0; + get_active_item_initial_point(x0, y0); + + int y1 = y0 + total_dy; + dy = y1 - current_pos.get_y(); + + int x1 = x0 + total_dx; + // snap to frames + float fps = widget.canvas_interface->get_canvas()->rend_desc().get_frame_rate(); + Time next_t = widget.time_plot_data->get_t_from_pixel_coord(x1).round(fps); + x1 = widget.time_plot_data->get_pixel_t_coord(next_t); + dx = x1 - current_pos.get_x(); + } + + std::vector selection = get_selected_items(); + // Move Y value + if (dy != 0) { + // If the active point (ie. the point clicked and dragged by cursor) is a converted parameter, + // it can't be moved vertically. + // So, any selected channel point shouldn't be moved vertically: it is strange in UX point of view + if (get_active_item()->is_draggable()) { + std::map< long, std::map< Time, std::vector > > selection_tree; + for (auto point : selection) { + // If it is a converted parameter (and not its inner parameter), its Y value can't be changed + if (point->is_draggable()) + selection_tree[std::distance(widget.curve_list.begin(), point->curve_it)][point->time_point.get_time()].push_back(point); + } + + for (const auto &curve : selection_tree) { + for (auto tt : curve.second) { + auto time = tt.first; + auto channels = tt.second; + auto refpoint = channels.front(); + ValueBase value_base = refpoint->curve_it->value_desc.get_value(time); + + for (auto point : channels) { + // Clear cached values due to precision error while dragging multiple points + point->curve_it->channels[point->channel_idx].values.clear(); + + Real v = point->get_value(widget.time_plot_data->dt); + int pix_y = widget.time_plot_data->get_pixel_y_coord(v); + pix_y += dy; + v = widget.time_plot_data->get_y_from_pixel_coord(pix_y); + CurveStruct::set_value_base_channel_value(value_base, point->channel_idx, v); + } + + const ValueDesc &value_desc = refpoint->curve_it->value_desc; + ValueNode::Handle value_node = value_desc.get_value_node(); + std::set > waypoint_set; + synfig::waypoint_collect(waypoint_set, time, value_node); + if (waypoint_set.size() < 1) + break; + + Waypoint waypoint(*(waypoint_set.begin())); + waypoint.set_value(value_base); + widget.canvas_interface->waypoint_set_value_node(value_node, waypoint); + } + } + } + } + + // Move along time + if (dx == 0) + return; + std::vector> times_to_move; + + const float fps = widget.canvas_interface->get_canvas()->rend_desc().get_frame_rate(); + const Time base_time = get_active_item()->time_point.get_time(); + const Time next_time = widget.time_plot_data->get_t_from_pixel_coord(widget.time_plot_data->get_pixel_t_coord(base_time) + dx).round(fps); + const Time deltatime = next_time - base_time; + + if (deltatime != 0) { + // new dragging position allow us to restore previouly overlapped waypoints + auto waypoints_to_restore = widget.overlapped_waypoints; + // let it store new overlapped waypoints until next drag motion + widget.overlapped_waypoints.clear(); + + bool ignore_move = false; + std::vector > timepoints_to_update; + + // sort in order to avoid overlapping issues + std::sort(selection.begin(), selection.end(), [=](const ChannelPoint * const a, const ChannelPoint * const b)->bool { + if (dx < 0) + return a->time_point < b->time_point; + else + return b->time_point < a->time_point; + }); + + for (ChannelPoint * point : selection) { + const ValueDesc &value_desc = point->curve_it->value_desc; + const Time &time = point->time_point.get_time(); + const Time new_time = Time(time+deltatime).round(fps); + + std::pair meta_data(value_desc, time); + if (std::find(times_to_move.begin(), times_to_move.end(), meta_data) == times_to_move.end()) { + auto time_set = WaypointRenderer::get_times_from_valuedesc(value_desc); + + // are we overlapping existing waypoints while moving in time? + auto new_timepoint = time_set.find(new_time); + if (new_timepoint != time_set.end()) { + // Converted layer parameter can't overlap waypoints... So, nobody moves this time + if (!point->is_draggable()) { + ignore_move = true; + times_to_move.clear(); + break; + } + // Check if the point is selected to move. If so, it won't overlap + ChannelPoint test_point(*point); + test_point.time_point = *new_timepoint; + if (!is_waypoint_selected(test_point)) { + std::set > waypoint_set; + synfig::waypoint_collect(waypoint_set, new_time, value_desc.get_value_node()); + for (const Waypoint &waypoint : waypoint_set) { + widget.overlapped_waypoints.push_back(std::pair::iterator>(waypoint, point->curve_it)); + widget.canvas_interface->waypoint_remove(value_desc, waypoint); + } + } + } + + times_to_move.push_back(meta_data); + } + + timepoints_to_update.push_back(std::pair(point, new_time)); + } + + if (!ignore_move) { + // first we move waypoints + for (const auto &info : times_to_move) + widget.canvas_interface->waypoint_move(info.first, info.second, deltatime); + // now we update cached values in select-drag handler + for (auto pair : timepoints_to_update) + pair.first->time_point = TimePoint(pair.second); + } + + // Now we can restore previously overlapped waypoints + for (auto it : waypoints_to_restore) { + Action::Handle action(Action::create("WaypointAdd")); + + assert(action); + if(!action) + return; + + action->set_param("canvas", widget.canvas_interface->get_canvas()); + action->set_param("widget.canvas_interface", widget.canvas_interface); + action->set_param("value_node", it.second->value_desc.get_value_node()); +// action->set_param("time", i.first.get_time()); + action->set_param("waypoint", it.first); + + if(!widget.canvas_interface->get_instance()->perform_action(action)) + widget.canvas_interface->get_ui_interface()->error(_("Action Failed.")); + } + } + + widget.queue_draw(); +} + +bool Widget_Curves::ChannelPointSD::is_waypoint_selected(const Widget_Curves::ChannelPoint& point) const +{ + ChannelPoint test_point(point); + const size_t n_channels = point.curve_it->channels.size(); + for (size_t cidx = 0; cidx < n_channels; cidx++) { + test_point.channel_idx = cidx; + if (is_selected(test_point)) + return true; + } + return false; +} + diff --git a/synfig-studio/src/gui/widgets/widget_curves.h b/synfig-studio/src/gui/widgets/widget_curves.h index 26b110c..9c63aed 100644 --- a/synfig-studio/src/gui/widgets/widget_curves.h +++ b/synfig-studio/src/gui/widgets/widget_curves.h @@ -34,47 +34,124 @@ #include #include +#include #include +#include "selectdraghelper.h" + /* === M A C R O S ========================================================= */ /* === T Y P E D E F S ===================================================== */ /* === C L A S S E S & S T R U C T S ======================================= */ +namespace synfigapp { +namespace Action { +class PassiveGrouper; +} +} + namespace studio { +struct TimePlotData; + class Widget_Curves: public Gtk::DrawingArea { + friend class ChannelPointSD; private: struct Channel; struct CurveStruct; + struct ChannelPoint { + std::list::iterator curve_it; + synfig::TimePoint time_point; + size_t channel_idx; + + ChannelPoint(); + ChannelPoint(std::list::iterator &curve_it, const synfig::TimePoint time_point, size_t channel_idx); + + void invalidate(); + bool is_valid() const; + bool is_draggable() const; + + bool operator ==(const ChannelPoint &b) const; + bool operator !=(const ChannelPoint &b) const {return !operator==(b);} + + synfig::Real get_value(synfig::Real time_tolerance) const; + }; + + class ChannelPointSD : public SelectDragHelper { + Widget_Curves & widget; + public: + ChannelPointSD(Widget_Curves &widget); + virtual ~ChannelPointSD() override {} + + void get_item_position(const ChannelPoint &item, Gdk::Point &position) override; + + bool find_item_at_position(int pos_x, int pos_y, ChannelPoint & cp) override; + bool find_items_in_rect(Gdk::Rectangle rect, std::vector & list) override; + void get_all_items(std::vector & items) override; + void delta_drag(int total_dx, int total_dy, bool by_keys) override; + + // Check if waypoint (of curve_it at time_point no matter channel) is selected + bool is_waypoint_selected(const ChannelPoint& point) const; + } channel_point_sd; + + etl::handle canvas_interface; Glib::RefPtr range_adjustment; - etl::handle time_model; std::list curve_list; - sigc::connection time_changed; std::list value_desc_changed; + TimePlotData * time_plot_data; + + int waypoint_edge_length; + + std::vector::iterator> > overlapped_waypoints; + + void on_waypoint_clicked(const ChannelPoint &cp, unsigned int button, Gdk::Point /*point*/); + void on_waypoint_double_clicked(const ChannelPoint &cp, unsigned int button, Gdk::Point /*point*/); + + sigc::signal >, int> signal_waypoint_clicked_; + sigc::signal >, int> signal_waypoint_double_clicked_; + public: Widget_Curves(); ~Widget_Curves(); const Glib::RefPtr& get_range_adjustment() const { return range_adjustment; } - const etl::handle& get_time_model() const { return time_model; } + const etl::handle& get_time_model() const; void set_time_model(const etl::handle &x); - void set_value_descs(const std::list &value_descs); + void set_value_descs(etl::handle canvas_interface_, const std::list< std::pair > &data); void clear(); void refresh(); + void zoom_in(); + void zoom_out(); + void zoom_100(); + void set_zoom(double new_zoom_factor); + double get_zoom() const; + + void scroll_up(); + void scroll_down(); + + void pan(int dx, int dy, int total_dx, int total_dy); + + void select_all_points(); + + sigc::signal >, int>& signal_waypoint_clicked() { return signal_waypoint_clicked_; } + sigc::signal >, int>& signal_waypoint_double_clicked() { return signal_waypoint_double_clicked_; } + protected: - bool on_draw(const Cairo::RefPtr &cr); bool on_event(GdkEvent *event); + bool on_draw(const Cairo::RefPtr &cr); + + void delete_selected(); + bool add_waypoint_to(int point_x, int point_y); }; // END of class Widget_Curves }; // END of namespace studio diff --git a/synfig-studio/src/gui/widgets/widget_timeslider.cpp b/synfig-studio/src/gui/widgets/widget_timeslider.cpp index fb0be4e..e200ad9 100644 --- a/synfig-studio/src/gui/widgets/widget_timeslider.cpp +++ b/synfig-studio/src/gui/widgets/widget_timeslider.cpp @@ -46,6 +46,8 @@ #include +#include "gui/timeplotdata.h" + #endif /* === U S I N G =========================================================== */ @@ -166,37 +168,36 @@ Widget_Timeslider::Widget_Timeslider(): play_bounds_pattern->set_extend(Cairo::EXTEND_REPEAT); } + time_plot_data = new TimePlotData(*this); + time_plot_data->set_extra_time_margin(get_height()); + // click / scroll / zoom add_events( Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::BUTTON_MOTION_MASK | Gdk::SCROLL_MASK ); + + signal_configure_event().connect( + sigc::mem_fun(*this, &Widget_Timeslider::on_configure_event)); } Widget_Timeslider::~Widget_Timeslider() { time_change.disconnect(); time_bounds_change.disconnect(); + delete time_plot_data; +} + +const etl::handle& +Widget_Timeslider::get_time_model() const +{ + return time_plot_data->time_model; } void Widget_Timeslider::set_time_model(const etl::handle &x) { - if (time_model == x) return; - - //disconnect old connections - time_change.disconnect(); - time_bounds_change.disconnect(); - - //connect update function to new adjustment - time_model = x; - - if (time_model) { - time_change = time_model->signal_time_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw)); - time_bounds_change = time_model->signal_play_time_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw)); - } - - queue_draw(); + time_plot_data->set_time_model(x); } void @@ -210,6 +211,12 @@ Widget_Timeslider::draw_background(const Cairo::RefPtr &cr) cr->restore(); } +bool Widget_Timeslider::on_configure_event(GdkEventConfigure* configure) +{ + time_plot_data->set_extra_time_margin(configure->height); + return false; +} + bool Widget_Timeslider::on_draw(const Cairo::RefPtr &cr) { @@ -218,22 +225,12 @@ Widget_Timeslider::on_draw(const Cairo::RefPtr &cr) draw_background(cr); - if (!time_model || get_width() <= 0 || get_height() <= 0) return true; - - // Get the time information since we now know it's valid - Time time = time_model->get_time(); - Time lower = time_model->get_visible_lower(); - Time upper = time_model->get_visible_upper(); + if (!time_plot_data->time_model || get_width() <= 0 || get_height() <= 0) return true; - if (lower >= upper) return true; - double k = (double)get_width()/(double)(upper - lower); - - Time extra_time = (double)get_height()/k; - Time lower_ex = lower - extra_time; - Time upper_ex = upper + extra_time; + const etl::handle & time_model = time_plot_data->time_model; // Draw the time line... - double tpx = etl::round_to_int((time - lower)*k) + 0.5; + double tpx = time_plot_data->get_pixel_t_coord(time_plot_data->time) + 0.5; cr->save(); cr->set_source_rgb(1.0, 175.0/255.0, 0.0); cr->set_line_width(1.0); @@ -249,8 +246,8 @@ Widget_Timeslider::on_draw(const Cairo::RefPtr &cr) int subdivisions; calc_divisions( time_model->get_frame_rate(), - 140.0/k, - 280.0/k, + 140.0/time_plot_data->k, + 280.0/time_plot_data->k, big_step_value, subdivisions ); @@ -258,15 +255,15 @@ Widget_Timeslider::on_draw(const Cairo::RefPtr &cr) step = std::max(time_model->get_step_increment(), step); Time big_step = step * (double)subdivisions; - Time current = big_step * floor((double)lower_ex/(double)big_step); + Time current = big_step * floor((double)time_plot_data->lower_ex/(double)big_step); current = time_model->round_time(current); // draw cr->save(); cr->set_source_rgb(51.0/255.0,51.0/255.0,51.0/255.0); cr->set_line_width(1.0); - for(int i = 0; current <= upper_ex; ++i, current = time_model->round_time(current + step)) { - double x = etl::round_to_int((double)(current - lower)*k) + 0.5; + for(int i = 0; current <= time_plot_data->upper_ex; ++i, current = time_model->round_time(current + step)) { + double x = time_plot_data->get_pixel_t_coord(current) + 0.5; if (i % subdivisions == 0) { // draw big cr->move_to(x, 0.0); @@ -302,21 +299,21 @@ Widget_Timeslider::on_draw(const Cairo::RefPtr &cr) // Draw the time line Gdk::Cairo::set_source_color(cr, Gdk::Color("#ffaf00")); cr->set_line_width(3.0); - double x = round((double)(time - lower)*k); + double x = time_plot_data->get_pixel_t_coord(time_plot_data->time); cr->move_to(x, 0.0); cr->line_to(x, fullheight); cr->stroke(); // Draw play bounds if (time_model->get_play_bounds_enabled()) { - double offset = -round((double)lower*k); + double offset = time_plot_data->get_double_pixel_t_coord(0); Time bounds[2][2] { - { lower_ex, time_model->get_play_bounds_lower() }, - { time_model->get_play_bounds_upper(), upper_ex } }; + { time_plot_data->lower_ex, time_model->get_play_bounds_lower() }, + { time_model->get_play_bounds_upper(), time_plot_data->upper_ex } }; for(int i = 0; i < 2; ++i) { if (bounds[i][0] < bounds[i][1]) { - double x0 = round((double)(bounds[i][0] - lower)*k); - double x1 = round((double)(bounds[i][1] - lower)*k); + double x0 = time_plot_data->get_double_pixel_t_coord(bounds[i][0]); + double x1 = time_plot_data->get_double_pixel_t_coord(bounds[i][1]); double w = x1 - x0; cr->save(); @@ -348,18 +345,12 @@ Widget_Timeslider::on_button_press_event(GdkEventButton *event) //for clicking { lastx = (double)event->x; - if (!time_model || get_width() <= 0 || get_height() <= 0) - return false; - - Time lower = time_model->get_visible_lower(); - Time upper = time_model->get_visible_upper(); - if (lower >= upper) + if (!time_plot_data->time_model || get_width() <= 0 || get_height() <= 0) return false; if (event->button == 1) { - double k = (upper - lower)/(double)get_width(); - Time time = Time((double)event->x*k) + lower; - time_model->set_time(time); + Time time = time_plot_data->get_t_from_pixel_coord(event->x); + time_plot_data->time_model->set_time(time); } return event->button == 1 || event->button == 2; @@ -377,26 +368,20 @@ Widget_Timeslider::on_motion_notify_event(GdkEventMotion* event) //for dragging double dx = (double)event->x - lastx; lastx = (double)event->x; - if (!time_model || get_width() <= 0 || get_height() <= 0) - return false; - - Time lower = time_model->get_visible_lower(); - Time upper = time_model->get_visible_upper(); - if (lower >= upper) + if (!time_plot_data->time_model || get_width() <= 0 || get_height() <= 0) return false; - double k = (upper - lower)/(double)get_width(); Gdk::ModifierType mod = Gdk::ModifierType(event->state); if (mod & Gdk::BUTTON1_MASK) { // scrubbing - Time time = Time((double)event->x*k) + lower; - time_model->set_time(time); + Time time = time_plot_data->get_t_from_pixel_coord(event->x); + time_plot_data->time_model->set_time(time); return true; } else if (mod & Gdk::BUTTON2_MASK) { // scrolling - Time dt(-dx*k); - time_model->move_by(dt); + Time dt(-dx*time_plot_data->dt); + time_plot_data->time_model->move_by(dt); return true; } @@ -406,16 +391,12 @@ Widget_Timeslider::on_motion_notify_event(GdkEventMotion* event) //for dragging bool Widget_Timeslider::on_scroll_event(GdkEventScroll* event) //for zooming { - if (!time_model || get_width() <= 0 || get_height() <= 0) - return false; + etl::handle &time_model = time_plot_data->time_model; - Time lower = time_model->get_visible_lower(); - Time upper = time_model->get_visible_upper(); - if (lower >= upper) + if (!time_model || get_width() <= 0 || get_height() <= 0) return false; - double k = (upper - lower)/(double)get_width(); - Time time = Time((double)event->x*k) + lower; + Time time = time_plot_data->get_t_from_pixel_coord(event->x); switch(event->direction) { case GDK_SCROLL_UP: //zoom in diff --git a/synfig-studio/src/gui/widgets/widget_timeslider.h b/synfig-studio/src/gui/widgets/widget_timeslider.h index fe5b87b..a63992d 100644 --- a/synfig-studio/src/gui/widgets/widget_timeslider.h +++ b/synfig-studio/src/gui/widgets/widget_timeslider.h @@ -42,6 +42,8 @@ namespace studio { +class TimePlotData; + //! Design for the timeslider... //! Concept: Scalable ruler //! Ticks are done every so often (30 s, 10 frames, 5 frames, etc.) @@ -52,8 +54,6 @@ class Widget_Timeslider: public Gtk::DrawingArea protected: // implementation that other interfaces can see Glib::RefPtr layout; // implementation awesomeness for text drawing - etl::handle time_model; - Cairo::RefPtr play_bounds_pattern; // last mouse position for dragging @@ -65,6 +65,8 @@ protected: // implementation that other interfaces can see sigc::connection time_change; sigc::connection time_bounds_change; + TimePlotData * time_plot_data; + virtual bool on_button_press_event(GdkEventButton *event); //for clicking virtual bool on_button_release_event(GdkEventButton *event); //for clicking virtual bool on_motion_notify_event(GdkEventMotion* event); //for dragging @@ -73,11 +75,13 @@ protected: // implementation that other interfaces can see virtual void draw_background(const Cairo::RefPtr &cr); + virtual bool on_configure_event(GdkEventConfigure * configure); + public: Widget_Timeslider(); ~Widget_Timeslider(); - const etl::handle& get_time_model() const { return time_model; } + const etl::handle& get_time_model() const; void set_time_model(const etl::handle &x); }; diff --git a/synfig-studio/src/gui/workarea.cpp b/synfig-studio/src/gui/workarea.cpp index 51c35fd..155312f 100644 --- a/synfig-studio/src/gui/workarea.cpp +++ b/synfig-studio/src/gui/workarea.cpp @@ -90,7 +90,7 @@ using namespace studio; /* === M A C R O S ========================================================= */ -#define THUMB_SIZE 128; +#define THUMB_SIZE 128 #ifndef stratof #define stratof(X) (atof((X).c_str())) diff --git a/synfig-studio/src/synfigapp/canvasinterface.cpp b/synfig-studio/src/synfigapp/canvasinterface.cpp index 07e19b5..1bf03e3 100644 --- a/synfig-studio/src/synfigapp/canvasinterface.cpp +++ b/synfig-studio/src/synfigapp/canvasinterface.cpp @@ -1136,6 +1136,47 @@ CanvasInterface::import_sequence( return true; } +void CanvasInterface::waypoint_set_value_node(ValueNode::Handle value_node, const Waypoint& waypoint) +{ + Action::Handle action(Action::create("WaypointSetSmart")); + + assert(action); + if(!action) + return; + + action->set_param("canvas", get_canvas()); + action->set_param("canvas_interface", this); + action->set_param("value_node", value_node); + action->set_param("waypoint", waypoint); +// action->set_param("time",canvas_interface()->get_time()); + + if(!get_instance()->perform_action(action)) + get_ui_interface()->error(_("Action Failed.")); +} + +void CanvasInterface::waypoint_move(const ValueDesc& value_desc, const Time& time, const Time& deltatime) +{ + Action::Handle action(Action::create("TimepointsMove")); + + assert(action); + if(!action) + return; + + action->set_param("canvas", get_canvas()); + action->set_param("canvas_interface", this); + if (value_desc.get_value_type() == type_canvas && !getenv("SYNFIG_SHOW_CANVAS_PARAM_WAYPOINTS")) { + action->set_param("addcanvas", value_desc.get_value().get(Canvas::Handle())); + } else { + action->set_param("addvaluedesc", value_desc); + } + + action->set_param("addtime", time); + + action->set_param("deltatime", deltatime); + + if(!get_instance()->perform_action(action)) + get_ui_interface()->error(_("Action Failed.")); +} void CanvasInterface::waypoint_duplicate(synfigapp::ValueDesc value_desc,synfig::Waypoint waypoint) @@ -1248,8 +1289,13 @@ CanvasInterface::auto_export(const ValueDesc& /*value_desc*/) bool CanvasInterface::change_value(synfigapp::ValueDesc value_desc,synfig::ValueBase new_value,bool lock_animation) { + return change_value_at_time(value_desc, new_value, get_time(), lock_animation); +} + +bool CanvasInterface::change_value_at_time(ValueDesc value_desc, ValueBase new_value, const Time& time, bool lock_animation) +{ ValueBase old_value; - old_value = value_desc.get_value(get_time()); + old_value = value_desc.get_value(time); // If this isn't really a change, then don't bother if(new_value==old_value) @@ -1267,7 +1313,7 @@ CanvasInterface::change_value(synfigapp::ValueDesc value_desc,synfig::ValueBase instance=find_instance(value_desc.get_canvas()->get_root()); if(instance) - return instance->find_canvas_interface(value_desc.get_canvas())->change_value(value_desc,new_value); + return instance->find_canvas_interface(value_desc.get_canvas())->change_value_at_time(value_desc,new_value, time); else { get_ui_interface()->error(_("The value you are trying to edit is in a composition\nwhich doesn't seem to be open. Open that composition and you\nshould be able to edit this value as normal.")); @@ -1288,7 +1334,7 @@ CanvasInterface::change_value(synfigapp::ValueDesc value_desc,synfig::ValueBase action->set_param("canvas",get_canvas()); action->set_param("canvas_interface",etl::loose_handle(this)); - action->set_param("time",get_time()); + action->set_param("time",time); action->set_param("value_desc",value_desc); action->set_param("new_value",new_value); if (lock_animation) action->set_param("lock_animation", lock_animation); diff --git a/synfig-studio/src/synfigapp/canvasinterface.h b/synfig-studio/src/synfigapp/canvasinterface.h index 43b5928..d22efca 100644 --- a/synfig-studio/src/synfigapp/canvasinterface.h +++ b/synfig-studio/src/synfigapp/canvasinterface.h @@ -314,6 +314,8 @@ public: synfig::String &warnings, bool resize_image = false ); + void waypoint_set_value_node(synfig::ValueNode::Handle value_node, const synfig::Waypoint& waypoint); + void waypoint_move(const ValueDesc& value_desc, const synfig::Time& time, const synfig::Time& deltatime); void waypoint_duplicate(synfigapp::ValueDesc value_desc,synfig::Waypoint waypoint); void waypoint_duplicate(synfig::ValueNode::Handle value_node,synfig::Waypoint waypoint); @@ -322,6 +324,7 @@ public: void waypoint_remove(synfig::ValueNode::Handle value_node,synfig::Waypoint waypoint); bool change_value(synfigapp::ValueDesc value_desc,synfig::ValueBase new_value,bool lock_animation = false); + bool change_value_at_time(synfigapp::ValueDesc value_desc,synfig::ValueBase new_value,const synfig::Time &time,bool lock_animation = false); int find_important_value_descs(std::vector& out); diff --git a/synfig-studio/src/synfigapp/value_desc.h b/synfig-studio/src/synfigapp/value_desc.h index 792d8b4..88f2abb 100644 --- a/synfig-studio/src/synfigapp/value_desc.h +++ b/synfig-studio/src/synfigapp/value_desc.h @@ -456,7 +456,7 @@ public: return ValueDesc(*this, sub_name); } - //! @return copy of it self when sum_name is empty string + //! @return copy of it self when sub_name is empty string ValueDesc get_sub_value(const synfig::String &sub_name)const { return sub_name.empty() ? *this : ValueDesc(*this, sub_name);