diff --git a/toonz/sources/include/tools/modifiers/modifiersegmentation.h b/toonz/sources/include/tools/modifiers/modifiersegmentation.h
index 48d8872..e706d5c 100644
--- a/toonz/sources/include/tools/modifiers/modifiersegmentation.h
+++ b/toonz/sources/include/tools/modifiers/modifiersegmentation.h
@@ -32,7 +32,7 @@ private:
   void addSegments(TTrack &track, const TTrackPoint &p0, const TTrackPoint &p1, int maxLevel);
 
 public:
-  TModifierSegmentation(const TPointD &step = TPointD(1.0, 1.0), int level = 10);
+  explicit TModifierSegmentation(const TPointD &step = TPointD(1.0, 1.0), int level = 10);
 
   void setStep(const TPointD &step);
   inline const TPointD& getStep() const { return m_step; }
diff --git a/toonz/sources/include/tools/modifiers/modifiersimplify.h b/toonz/sources/include/tools/modifiers/modifiersimplify.h
new file mode 100644
index 0000000..6f353b8
--- /dev/null
+++ b/toonz/sources/include/tools/modifiers/modifiersimplify.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#ifndef MODIFIERSIMPLIFY_INCLUDED
+#define MODIFIERSIMPLIFY_INCLUDED
+
+// TnzTools includes
+#include <tools/inputmanager.h>
+
+
+#undef DVAPI
+#undef DVVAR
+#ifdef TNZTOOLS_EXPORTS
+#define DVAPI DV_EXPORT_API
+#define DVVAR DV_EXPORT_VAR
+#else
+#define DVAPI DV_IMPORT_API
+#define DVVAR DV_IMPORT_VAR
+#endif
+
+
+//===================================================================
+
+//*****************************************************************************************
+//    TModifierSimplify definition
+//*****************************************************************************************
+
+class DVAPI TModifierSimplify: public TInputModifier {
+private:
+  double step;
+  
+public:
+  explicit TModifierSimplify(double step = 1.0);
+
+  void setStep(double step);
+  inline double getStep() const { return step; }
+
+  void modifyTrack(
+    const TTrack &track,
+    TTrackList &outTracks ) override;
+};
+
+
+#endif
diff --git a/toonz/sources/include/tools/modifiers/modifiersmooth.h b/toonz/sources/include/tools/modifiers/modifiersmooth.h
index 6968d38..193db53 100644
--- a/toonz/sources/include/tools/modifiers/modifiersmooth.h
+++ b/toonz/sources/include/tools/modifiers/modifiersmooth.h
@@ -39,7 +39,7 @@ private:
   int m_radius;
   
 public:
-  TModifierSmooth(int radius = 10);
+  explicit TModifierSmooth(int radius = 10);
 
   void setRadius(int radius);
   int getRadius() const { return m_radius; }
diff --git a/toonz/sources/include/toonz/strokegenerator.h b/toonz/sources/include/toonz/strokegenerator.h
index bc96dc7..1d253f0 100644
--- a/toonz/sources/include/toonz/strokegenerator.h
+++ b/toonz/sources/include/toonz/strokegenerator.h
@@ -39,6 +39,9 @@ class DVAPI StrokeGenerator {
 
   //! Ultimo punto del frammento visualizzato
   TPointD m_p0, /*! Ultimo punto del frammento visualizzato*/ m_p1;
+  
+  //! mark that stroke must be looped
+  bool m_loop;
 
   //! Visualizza i frammenti
   /*!
@@ -66,10 +69,19 @@ public:
     di 4*pixelSize2
 
     \param point      TThickPoint da aggiungere al vettore
-    \param pixelSize2 Dimensione pixel
+    \param pixelSize2 Size of pixel, use 0 to guarantee that new point will be added
+    \returns          true if point was actually added
   */
-  void add(const TThickPoint &point, double pixelSize2);
-
+  bool add(const TThickPoint &point, double pixelSize2);
+  
+  //! Remove last point (keep in mind that not each 'add' call produces new point)
+  void pop();
+  
+  //! Mark/unmark track as looped
+  void setLoop(bool loop = true);
+  
+  inline bool getLoop() const { return m_loop; }
+  
   TPointD getFirstPoint();  // returns the first point
 
   //! Filtra i punti di m_points
@@ -107,7 +119,7 @@ onlyLastPoint elementi di m_points
           \param onlyLastPoints Numero elementi sulla base dei quali creare la
 stroke
   */
-  TStroke *makeStroke(double error, UINT onlyLastPoints = 0) const;
+  TStroke *makeStroke(double error, UINT onlyLastPoints = 0, bool useLoop = false) const;
 };
 
 //===============================================================
diff --git a/toonz/sources/tnztools/CMakeLists.txt b/toonz/sources/tnztools/CMakeLists.txt
index 2675853..2a3d441 100644
--- a/toonz/sources/tnztools/CMakeLists.txt
+++ b/toonz/sources/tnztools/CMakeLists.txt
@@ -54,6 +54,7 @@ set(HEADERS
     ../include/tools/modifiers/modifiertangents.h
     ../include/tools/modifiers/modifiertest.h
     ../include/tools/modifiers/modifiersegmentation.h
+    ../include/tools/modifiers/modifiersimplify.h
     ../include/tools/modifiers/modifiersmooth.h
     ../include/tools/assistants/guidelineline.h
     ../include/tools/assistants/guidelineellipse.h
@@ -130,6 +131,7 @@ set(SOURCES
     modifiers/modifiertangents.cpp
     modifiers/modifiertest.cpp
     modifiers/modifiersegmentation.cpp
+    modifiers/modifiersimplify.cpp
     modifiers/modifiersmooth.cpp
     assistants/guidelineline.cpp
     assistants/guidelineellipse.cpp
diff --git a/toonz/sources/tnztools/modifiers/modifiersimplify.cpp b/toonz/sources/tnztools/modifiers/modifiersimplify.cpp
new file mode 100644
index 0000000..85b66b3
--- /dev/null
+++ b/toonz/sources/tnztools/modifiers/modifiersimplify.cpp
@@ -0,0 +1,78 @@
+
+
+#include <tools/modifiers/modifiersimplify.h>
+#include <algorithm>
+
+
+//*****************************************************************************************
+//    TModifierSimplify implementation
+//*****************************************************************************************
+
+
+TModifierSimplify::TModifierSimplify(double step):
+  step() { setStep(step); }
+
+
+void
+TModifierSimplify::setStep(double step)
+  { this->step = std::max(0.0, step); }
+
+
+void
+TModifierSimplify::modifyTrack(
+  const TTrack &track,
+  TTrackList &outTracks )
+{
+  if (!track.handler) {
+    track.handler = new TTrackHandler(track);
+    track.handler->tracks.push_back(
+      new TTrack(
+        new TTrackModifier(*track.handler) ));
+  }
+
+  if (track.handler->tracks.empty())
+    return;
+
+  TTrack &subTrack = *track.handler->tracks.front();
+  outTracks.push_back(track.handler->tracks.front());
+
+  if (!track.changed())
+    return;
+
+  // remove points
+  int start = track.size() - track.pointsAdded;
+  if (start < 0) start = 0;
+  int subStart = subTrack.floorIndex(subTrack.indexByOriginalIndex(start));
+  if (subStart < 0) subStart = 0;
+  start = track.floorIndex(subTrack[subStart].originalIndex);
+  if (start < 0) start = 0;
+  subTrack.truncate(subStart);
+
+  // add points
+  double step2 = step*step;
+  TTrackPoint p0 = subTrack.back();
+  for(int i = start; i < track.size(); ++i) {
+    const TTrackPoint &p1 = track[i];
+    if (!subTrack.empty() && tdistance2(p1.position, p0.position) < step2) {
+      if (p0.pressure < p1.pressure) p0.pressure = p1.pressure;
+      p0.tilt          = p1.tilt;
+      p0.time          = p1.time;
+      p0.final         = p1.final;
+      subTrack.pop_back();
+      subTrack.push_back(p0, false);
+    } else {
+      p0 = p1;
+      p0.originalIndex = i;
+      subTrack.push_back(p0, false);
+    }
+  }
+
+  // fix points
+  if (track.fixedFinished())
+    subTrack.fix_all();
+  else
+    subTrack.fix_to(
+      subTrack.floorIndex( subTrack.indexByOriginalIndex(track.fixedSize()) ));
+  
+  track.resetChanges();
+}
diff --git a/toonz/sources/tnztools/toonzvectorbrushtool.cpp b/toonz/sources/tnztools/toonzvectorbrushtool.cpp
index 6198b6b..bb58efc 100644
--- a/toonz/sources/tnztools/toonzvectorbrushtool.cpp
+++ b/toonz/sources/tnztools/toonzvectorbrushtool.cpp
@@ -567,6 +567,22 @@ ToonzVectorBrushTool::ToonzVectorBrushTool(std::string name, int targetType)
   m_capStyle.setId("Cap");
   m_joinStyle.setId("Join");
   m_miterJoinLimit.setId("Miter");
+
+  m_inputmanager.setHandler(this);
+  m_modifierLine               = new TModifierLine();
+  m_modifierTangents           = new TModifierTangents();
+  m_modifierAssistants         = new TModifierAssistants();
+  m_modifierSegmentation       = new TModifierSegmentation();
+  m_modifierSmoothSegmentation = new TModifierSegmentation(TPointD(1, 1), 3);
+  for(int i = 0; i < 3; ++i)
+    m_modifierSmooth[i]        = new TModifierSmooth();
+  m_modifierSimplify           = new TModifierSimplify();
+#ifndef NDEBUG
+  m_modifierTest = new TModifierTest(5, 40);
+#endif
+
+  m_inputmanager.addModifier(
+      TInputModifierP(m_modifierAssistants.getPointer()));
 }
 
 //-------------------------------------------------------------------------------------------------------
@@ -649,46 +665,93 @@ void ToonzVectorBrushTool::onDeactivate() {
 
 //--------------------------------------------------------------------------------------------------
 
-bool ToonzVectorBrushTool::preLeftButtonDown() {
-  if (getViewer() && getViewer()->getGuidedStrokePickerMode()) return false;
+void ToonzVectorBrushTool::inputMouseMove(
+  const TPointD &position, const TInputState &state )
+{
+  struct Locals {
+    ToonzVectorBrushTool *m_this;
 
-  touchImage();
-  if (m_isFrameCreated) {
-    // When the xsheet frame is selected, whole viewer will be updated from
-    // SceneViewer::onXsheetChanged() on adding a new frame.
-    // We need to take care of a case when the level frame is selected.
-    if (m_application->getCurrentFrame()->isEditingLevel()) invalidate();
-  }
-  return true;
-}
+    void setValue(TDoublePairProperty &prop,
+                  const TDoublePairProperty::Value &value) {
+      prop.setValue(value);
 
-//--------------------------------------------------------------------------------------------------
+      m_this->onPropertyChanged(prop.getName());
+      TTool::getApplication()->getCurrentTool()->notifyToolChanged();
+    }
 
-void ToonzVectorBrushTool::leftButtonDown(const TPointD &pos,
-                                          const TMouseEvent &e)
-{
-  m_active = false;
+    void addMinMax(TDoublePairProperty &prop, double min, double max) {
+      if (min == 0.0 && max == 0.0) return;
+      const TDoublePairProperty::Range &range = prop.getRange();
+
+      TDoublePairProperty::Value value = prop.getValue();
+      value.first += min;
+      value.second += max;
+      if (value.first > value.second) value.first = value.second;
+      value.first  = tcrop(value.first, range.first, range.second);
+      value.second = tcrop(value.second, range.first, range.second);
+
+      setValue(prop, value);
+    }
+
+  } locals = {this};
+
+  TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5);
+  TRectD invalidateRect(m_brushPos - halfThick, m_brushPos + halfThick);
+
+  bool alt     = state.isKeyPressed(TInputState::Key::alt);
+  bool shift   = state.isKeyPressed(TInputState::Key::shift);
+  bool control = state.isKeyPressed(TInputState::Key::control);
   
-  TTool::Application *app = TTool::getApplication();
-  if (!app)
-    return;
+  if ( alt && control && !shift
+    && Preferences::instance()->useCtrlAltToResizeBrushEnabled() )
+  {
+    // Resize the brush if CTRL+ALT is pressed and the preference is enabled.
+    const TPointD &diff = position - m_mousePos;
+    double max          = diff.x / 2;
+    double min          = diff.y / 2;
 
-  if (getViewer() && getViewer()->getGuidedStrokePickerMode()) {
-    getViewer()->doPickGuideStroke(pos);
-    return;
-  }
+    locals.addMinMax(m_thickness, min, max);
 
-  m_isPath = app->getCurrentObject()->isSpline();
+    double radius = m_thickness.getValue().second * 0.5;
+    halfThick = TPointD(radius, radius);
+  } else {
+    m_brushPos = m_mousePos = position;
+  }
   
-  // todo: gestire autoenable
-  if ( !m_isPath
-    && app->getCurrentColumn()->getColumnIndex() < 0
-    && !app->getCurrentFrame()->isEditingLevel() )
-    return;
+  invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick);
+
+  if (m_minThick == 0 && m_maxThick == 0) {
+    m_minThick = m_thickness.getValue().first;
+    m_maxThick = m_thickness.getValue().second;
+  }
+
+  invalidate(invalidateRect.enlarge(2));
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void ToonzVectorBrushTool::inputSetBusy(bool busy) {
+  if (m_active == busy) return;
   
-  if (m_isPath) {
-    m_currentColor = TPixel32::Red;
-  } else {
+  if (busy) {
+    
+    // begin painting //////////////////////////
+    
+    TTool::Application *app = TTool::getApplication();
+    if (!app)
+      return;
+
+    m_isPath = app->getCurrentObject()->isSpline();
+    if (m_isPath) {
+      m_currentColor = TPixel32::Red;
+      m_active = true;
+      return;
+    }
+    
+    // todo: gestire autoenable
+    if ( app->getCurrentColumn()->getColumnIndex() < 0
+      && !app->getCurrentFrame()->isEditingLevel() )
+      return;
     if (!getImage(true) || !touchImage())
       return;
     if (!app->getCurrentLevel()->getLevel())
@@ -707,159 +770,32 @@ void ToonzVectorBrushTool::leftButtonDown(const TPointD &pos,
       m_styleId = 1;
       m_currentColor = TPixel32::Black;
     }
+    
+    m_active = true;
+    
+    return; // painting has begun
   }
   
-  m_active = true;
-
-  // assert(0<=m_styleId && m_styleId<2);
-  m_track.clear();
-  double thickness = computeThickness(e.m_pressure, m_thickness, m_pressure.getValue(), m_isPath);
-
-  /*--- ストロークの最初にMaxサイズの円が描かれてしまう不具合を防止する ---*/
-  if (m_pressure.getValue() && e.m_pressure == 1.0)
-    thickness = m_thickness.getValue().first * 0.5;
   
-  m_currThickness = thickness;
-  m_smoothStroke.beginStroke(m_smooth.getValue());
-
-  TPointD p = m_foundFirstSnap ? m_firstSnapPoint : pos;
-  addTrackPoint( TThickPoint(p, thickness), m_pixelSize*m_pixelSize );
-  TRectD invalidateRect = m_track.getLastModifiedRegion();
-  invalidate(invalidateRect.enlarge(2));
-
-  // updating m_brushPos is needed to refresh viewer properly
-  m_brushPos = m_mousePos = pos;
-}
-
-//-------------------------------------------------------------------------------------------------------------
-
-void ToonzVectorBrushTool::leftButtonDrag(const TPointD &pos,
-                                          const TMouseEvent &e) {
-  if (!m_active) {
-    m_brushPos = m_mousePos = pos;
-    return;
-  }
-
-  m_lastDragPos   = pos;
-  m_lastDragEvent = e;
-
-  double thickness = computeThickness(e.m_pressure, m_thickness, m_pressure.getValue(), m_isPath);
-
-  TRectD invalidateRect;
-  TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5);
-  TPointD snapThick(6.0 * m_pixelSize, 6.0 * m_pixelSize);
-
-  // In order to clear the previous brush tip
-  invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick);
-
-  // In order to clear the previous snap indicator
-  if (m_foundLastSnap)
-    invalidateRect +=
-        TRectD(m_lastSnapPoint - snapThick, m_lastSnapPoint + snapThick);
-
-  m_currThickness = thickness;
-
-  m_mousePos       = pos;
-  m_lastSnapPoint  = pos;
-  m_foundLastSnap  = false;
-  m_foundFirstSnap = false;
-  m_snapSelf       = false;
-  m_altPressed     = e.isAltPressed() && !e.isCtrlPressed();
-
-  checkStrokeSnapping(false, m_altPressed);
-  checkGuideSnapping(false, m_altPressed);
-  m_brushPos = m_lastSnapPoint;
-
-  if (m_foundLastSnap)
-    invalidateRect +=
-        TRectD(m_lastSnapPoint - snapThick, m_lastSnapPoint + snapThick);
-
-  if (e.isShiftPressed() && e.isCtrlPressed()) {
-    TPointD m_firstPoint = m_track.getFirstPoint();
-
-    double denominator = m_lastSnapPoint.x - m_firstPoint.x;
-    if (denominator == 0) denominator = 0.001;
-    double slope = ((m_brushPos.y - m_firstPoint.y) / denominator);
-    double angle = std::atan(slope) * (180 / 3.14159);
-    if (abs(angle) > 67.5)
-      m_lastSnapPoint.x = m_firstPoint.x;
-    else if (abs(angle) < 22.5)
-      m_lastSnapPoint.y = m_firstPoint.y;
-    else {
-      double xDistance = m_lastSnapPoint.x - m_firstPoint.x;
-      double yDistance = m_lastSnapPoint.y - m_firstPoint.y;
-      if (abs(xDistance) > abs(yDistance)) {
-        if (abs(yDistance) == yDistance)
-          m_lastSnapPoint.y = m_firstPoint.y + abs(xDistance);
-        else
-          m_lastSnapPoint.y = m_firstPoint.y - abs(xDistance);
-      } else {
-        if (abs(xDistance) == xDistance)
-          m_lastSnapPoint.x = m_firstPoint.x + abs(yDistance);
-        else
-          m_lastSnapPoint.x = m_firstPoint.x - abs(yDistance);
-      }
-    }
-
-    m_smoothStroke.clearPoints();
-    m_track.add(TThickPoint(m_lastSnapPoint, thickness),
-                getPixelSize() * getPixelSize());
-    m_track.removeMiddlePoints();
-    invalidateRect += m_track.getModifiedRegion();
-  } else if (e.isShiftPressed()) {
-    m_smoothStroke.clearPoints();
-    m_track.add(TThickPoint(m_brushPos, thickness),
-                getPixelSize() * getPixelSize());
-    m_track.removeMiddlePoints();
-    invalidateRect += m_track.getModifiedRegion();
-  } else if (m_dragDraw) {
-    addTrackPoint(TThickPoint(pos, thickness), getPixelSize() * getPixelSize());
-    invalidateRect += m_track.getLastModifiedRegion();
-  }
-
-  // In order to draw the current brush tip
-  invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick);
-
-  if (!invalidateRect.isEmpty()) {
-    // for motion path, call the invalidate function directly to ignore dpi of
-    // the current level
-    if (m_isPath)
-      m_viewer->GLInvalidateRect(invalidateRect.enlarge(2));
-    else
-      invalidate(invalidateRect.enlarge(2));
-  }
-}
-
-//---------------------------------------------------------------------------------------------------------------
-
-void ToonzVectorBrushTool::leftButtonUp(const TPointD &pos,
-                                        const TMouseEvent &e) {
-  if (!m_active) {
-    // in case the current frame is moved to empty cell while dragging
-    if (!m_track.isEmpty()) invalidate();
-    m_track.clear();
-    return;
-  }
+  // end painting //////////////////////////
+  
   m_active = false;
   
-
+  // clear tracks automatically when return from this function
+  struct Cleanup {
+    ToonzVectorBrushTool &owner;
+    inline ~Cleanup() { owner.m_track.clear(); owner.invalidate(); }
+  } cleanup = {*this};
+ 
+  
+  // make motion path (if need)
+    
   if (m_isPath) {
     double error = 20.0 * m_pixelSize;
 
-    TStroke *stroke;
-    if (e.isShiftPressed()) {
-      m_track.removeMiddlePoints();
-      stroke = m_track.makeStroke(0);
-    } else {
-      flushTrackPoint();
-      stroke = m_track.makeStroke(error);
-    }
+    TStroke *stroke = m_track.makeStroke(error);
 
     TVectorImageP vi = getImage(true);
-    struct Cleanup {
-      ToonzVectorBrushTool *m_this;
-      ~Cleanup() { m_this->m_track.clear(), m_this->invalidate(); }
-    } cleanup = {this};
 
     if (!isJustCreatedSpline(vi.getPointer())) {
       m_isPrompting = true;
@@ -873,8 +809,8 @@ void ToonzVectorBrushTool::leftButtonUp(const TPointD &pos,
 
     QMutexLocker lock(vi->getMutex());
 
-    TUndo *undo =
-        new UndoPath(getXsheet()->getStageObject(getObjectId())->getSpline());
+    TUndo *undo = new UndoPath(
+      getXsheet()->getStageObject(getObjectId())->getSpline() );
 
     while(vi->getStrokeCount() > 0) vi->deleteStroke(0);
     vi->addStroke(stroke, false);
@@ -882,35 +818,21 @@ void ToonzVectorBrushTool::leftButtonUp(const TPointD &pos,
     notifyImageChanged();
     TUndoManager::manager()->add(undo);
 
-    return;
+    return; // done with motion path
   }
+  
+  
+  // paint regular strokes
 
-  TVectorImageP vi = getImage(true);
-  if (m_track.isEmpty()) {
-    m_styleId = 0;
-    m_track.clear();
+  if (m_track.isEmpty())
     return;
-  }
-
-  if (vi && (m_snap.getValue() != m_altPressed) && m_foundLastSnap) {
-    addTrackPoint( TThickPoint(m_lastSnapPoint, m_currThickness),
-                   m_pixelSize*m_pixelSize );
-  }
-  m_foundFirstSnap = false;
-  m_foundLastSnap  = false;
 
+  
+  // prepare stroke
+  
   m_track.filterPoints();
-  double error = 30.0 / (1 + 0.5 * m_accuracy.getValue());
-  error *= getPixelSize();
-
-  TStroke *stroke;
-  if (e.isShiftPressed()) {
-    m_track.removeMiddlePoints();
-    stroke = m_track.makeStroke(0);
-  } else {
-    flushTrackPoint();
-    stroke = m_track.makeStroke(error);
-  }
+  double error = 30.0/(1 + 0.5 * m_accuracy.getValue())*m_pixelSize;
+  TStroke *stroke = m_track.makeStroke(error);
   
   stroke->setStyle(m_styleId);
   m_styleId = 0;
@@ -920,8 +842,6 @@ void ToonzVectorBrushTool::leftButtonUp(const TPointD &pos,
   options.m_joinStyle  = m_joinStyle.getIndex();
   options.m_miterUpper = m_miterJoinLimit.getValue();
 
-  QMutexLocker lock(vi->getMutex());
-  
   if ( stroke->getControlPointCount() == 3
     && stroke->getControlPoint(0) != stroke->getControlPoint(2) )
                                        // gli stroke con solo 1 chunk vengono
@@ -930,9 +850,17 @@ void ToonzVectorBrushTool::leftButtonUp(const TPointD &pos,
                                        // autoclose proprio dal fatto che
                                        // hanno 1 solo chunk.
     stroke->insertControlPoints(0.5);
+
   
+  // lock image
+
+  TVectorImageP vi = getImage(true);
+  QMutexLocker lock(vi->getMutex());
   TTool::Application *app = TTool::getApplication();
   
+  
+  // add stroke to image
+  
   if (m_frameRange.getIndex()) {
     if (m_firstFrameId == -1) {
       m_firstStroke  = new TStroke(*stroke);
@@ -964,7 +892,7 @@ void ToonzVectorBrushTool::leftButtonUp(const TPointD &pos,
           m_frameRange.getIndex(), m_breakAngles.getValue(), false, false,
           m_firstFrameRange );
       
-      if (e.isCtrlPressed()) {
+      if (m_inputmanager.state.isKeyPressed(TInputState::Key::control)) {
         if (app && m_firstFrameId > currentId) {
           if (app->getCurrentFrame()->isEditingScene()) {
             app->getCurrentColumn()->setColumnIndex(curCol);
@@ -991,48 +919,165 @@ void ToonzVectorBrushTool::leftButtonUp(const TPointD &pos,
         resetFrameRange();
       }
     }
-    
-    invalidate();
   } else {
-    if (m_snapSelf) {
+    if (m_track.getLoop())
       stroke->setSelfLoop(true);
-      m_snapSelf = false;
-    }
 
     addStrokeToImage(app, vi, stroke, m_breakAngles.getValue(),
                      false, false, m_isFrameCreated, m_isLevelCreated);
-    TRectD bbox = stroke->getBBox().enlarge(2) + m_track.getModifiedRegion();
-
-    invalidate();  // should use bbox?
 
     if ((Preferences::instance()->getGuidedDrawingType() == 1 ||
          Preferences::instance()->getGuidedDrawingType() == 2) &&
         Preferences::instance()->getGuidedAutoInbetween())
     {
-      int fidx     = app->getCurrentFrame()->getFrameIndex();
       TFrameId fId = getFrameId();
-
       doGuidedAutoInbetween(fId, vi, stroke, m_breakAngles.getValue(), false,
                             false, false);
-
       if (app->getCurrentFrame()->isEditingScene())
-        app->getCurrentFrame()->setFrame(fidx);
+        app->getCurrentFrame()->setFrame( app->getCurrentFrame()->getFrameIndex() );
       else
         app->getCurrentFrame()->setFid(fId);
     }
   }
   
-  assert(stroke);
-  m_track.clear();
-  m_altPressed = false;
+  delete stroke;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void ToonzVectorBrushTool::inputPaintTracks(const TTrackList &tracks) {
+  if (tracks.size() != 1 || !tracks.front()) return;
+
+  TRectD invalidateRect;
+  
+  const TTrack &track = *tracks.front();
+  while(track.pointsRemoved) {
+    m_track.pop();
+    --track.pointsRemoved;
+  }
+  
+  while(track.pointsAdded) {
+    const TTrackPoint &p = track.current();
+    double t = computeThickness(p.pressure, m_thickness, m_pressure.getValue(), m_isPath);
+    m_track.add(TThickPoint(p.position, t), 0);
+    --track.pointsAdded;
+  }
+  
+  invalidateRect += m_track.getLastModifiedRegion();
+
+  TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5);
+  invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick);
+  m_brushPos = m_mousePos = track.current().position;
+  invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick);
+  
+  if (!invalidateRect.isEmpty())
+    invalidate(invalidateRect.enlarge(2));
+}
+
+//--------------------------------------------------------------------------------------------------
+
+bool ToonzVectorBrushTool::preLeftButtonDown() {
+  if (getViewer() && getViewer()->getGuidedStrokePickerMode()) return false;
+
+  int smoothRadius = (int)round(m_smooth.getValue());
+  m_modifierAssistants->drawOnly = true; //bwtodo: !RasterBrushAssistants;
+  m_modifierSegmentation->setStep(TPointD(m_pixelSize, m_pixelSize));
+  m_modifierSmoothSegmentation->setStep(TPointD(2*m_pixelSize, 2*m_pixelSize));
+  m_modifierSimplify->setStep(2*m_pixelSize);
+  m_inputmanager.drawPreview = false;
+
+  m_inputmanager.clearModifiers();
+  m_inputmanager.addModifier(TInputModifierP(m_modifierTangents.getPointer()));
+  if (smoothRadius > 0) {
+    m_inputmanager.addModifier(TInputModifierP(m_modifierSmoothSegmentation.getPointer()));
+    for(int i = 0; i < 3; ++i) {
+      m_modifierSmooth[i]->setRadius(smoothRadius);
+      m_inputmanager.addModifier(TInputModifierP(m_modifierSmooth[i].getPointer()));
+    }
+  }
+  m_inputmanager.addModifier(TInputModifierP(m_modifierAssistants.getPointer()));
+#ifndef NDEBUG
+  m_inputmanager.addModifier(TInputModifierP(m_modifierTest.getPointer()));
+#endif
+  m_inputmanager.addModifier(TInputModifierP(m_modifierSegmentation.getPointer()));
+  m_inputmanager.addModifier(TInputModifierP(m_modifierSimplify.getPointer()));
+
+  touchImage();
+  if (m_isFrameCreated) {
+    // When the xsheet frame is selected, whole viewer will be updated from
+    // SceneViewer::onXsheetChanged() on adding a new frame.
+    // We need to take care of a case when the level frame is selected.
+    if (m_application->getCurrentFrame()->isEditingLevel()) invalidate();
+  }
+  return true;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void ToonzVectorBrushTool::handleMouseEvent(MouseEventType type,
+                                          const TPointD &pos,
+                                          const TMouseEvent &e)
+{
+  TTimerTicks t = TToolTimer::ticks();
+  bool alt      = e.getModifiersMask() & TMouseEvent::ALT_KEY;
+  bool shift    = e.getModifiersMask() & TMouseEvent::SHIFT_KEY;
+  bool control  = e.getModifiersMask() & TMouseEvent::CTRL_KEY;
+
+  if (shift && type == ME_DOWN && e.button() == Qt::LeftButton && !m_active) {
+    m_modifierAssistants->drawOnly = true;
+    m_inputmanager.clearModifiers();
+    m_inputmanager.addModifier(TInputModifierP(m_modifierLine.getPointer()));
+    m_inputmanager.addModifier(TInputModifierP(m_modifierAssistants.getPointer()));
+    m_inputmanager.addModifier(TInputModifierP(m_modifierSegmentation.getPointer()));
+    m_inputmanager.addModifier(TInputModifierP(m_modifierSimplify.getPointer()));
+    m_inputmanager.drawPreview = true;
+  }
+
+  if (alt != m_inputmanager.state.isKeyPressed(TKey::alt))
+    m_inputmanager.keyEvent(alt, TKey::alt, t, nullptr);
+  if (shift != m_inputmanager.state.isKeyPressed(TKey::shift))
+    m_inputmanager.keyEvent(shift, TKey::shift, t, nullptr);
+  if (control != m_inputmanager.state.isKeyPressed(TKey::control))
+    m_inputmanager.keyEvent(control, TKey::control, t, nullptr);
+
+  if (type == ME_MOVE) {
+    qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
+    THoverList hovers(1, pos);
+    m_inputmanager.hoverEvent(hovers);
+  } else
+  if (getViewer() && getViewer()->getGuidedStrokePickerMode()) {
+    if (type == ME_DOWN) getViewer()->doPickGuideStroke(pos);
+  } else {
+    m_inputmanager.trackEvent(e.isTablet(), 0, pos,
+                              &e.m_pressure, nullptr,
+                              type == ME_UP, t);
+    m_inputmanager.processTracks();
+  }
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void ToonzVectorBrushTool::leftButtonDown(const TPointD &pos,
+                                        const TMouseEvent &e) {
+  handleMouseEvent(ME_DOWN, pos, e);
+}
+void ToonzVectorBrushTool::leftButtonDrag(const TPointD &pos,
+                                        const TMouseEvent &e) {
+  handleMouseEvent(ME_DRAG, pos, e);
+}
+void ToonzVectorBrushTool::leftButtonUp(const TPointD &pos,
+                                      const TMouseEvent &e) {
+  handleMouseEvent(ME_UP, pos, e);
+}
+void ToonzVectorBrushTool::mouseMove(const TPointD &pos, const TMouseEvent &e) {
+  handleMouseEvent(ME_MOVE, pos, e);
 }
 
 //--------------------------------------------------------------------------------------------------
 
 bool ToonzVectorBrushTool::keyDown(QKeyEvent *event) {
-  if (event->key() == Qt::Key_Escape) {
+  if (event->key() == Qt::Key_Escape)
     resetFrameRange();
-  }
   return false;
 }
 
@@ -1050,14 +1095,14 @@ bool ToonzVectorBrushTool::doFrameRangeStrokes(
   TVectorImageP firstImage = new TVectorImage();
   TVectorImageP lastImage  = new TVectorImage();
 
-  *first       = *firstStroke;
-  *last        = *lastStroke;
-  bool swapped = false;
-  if (firstFrameId > lastFrameId) {
+  bool swapped = firstFrameId > lastFrameId;
+  if (swapped) {
     std::swap(firstFrameId, lastFrameId);
-    *first  = *lastStroke;
-    *last   = *firstStroke;
-    swapped = true;
+    *first = *lastStroke;
+    *last  = *firstStroke;
+  } else {
+    *first = *firstStroke;
+    *last  = *lastStroke;
   }
 
   firstImage->addStroke(first, false);
@@ -1258,97 +1303,6 @@ void ToonzVectorBrushTool::flushTrackPoint() {
   }
 }
 
-//---------------------------------------------------------------------------------------------------------------
-
-void ToonzVectorBrushTool::mouseMove(const TPointD &pos, const TMouseEvent &e) {
-  qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
-
-  struct Locals {
-    ToonzVectorBrushTool *m_this;
-
-    void setValue(TDoublePairProperty &prop,
-                  const TDoublePairProperty::Value &value) {
-      prop.setValue(value);
-
-      m_this->onPropertyChanged(prop.getName());
-      TTool::getApplication()->getCurrentTool()->notifyToolChanged();
-    }
-
-    void addMinMax(TDoublePairProperty &prop, double add) {
-      if (add == 0.0) return;
-      const TDoublePairProperty::Range &range = prop.getRange();
-
-      TDoublePairProperty::Value value = prop.getValue();
-      value.first  = tcrop(value.first + add, range.first, range.second);
-      value.second = tcrop(value.second + add, range.first, range.second);
-
-      setValue(prop, value);
-    }
-
-    void addMinMaxSeparate(TDoublePairProperty &prop, double min, double max) {
-      if (min == 0.0 && max == 0.0) return;
-      const TDoublePairProperty::Range &range = prop.getRange();
-
-      TDoublePairProperty::Value value = prop.getValue();
-      value.first += min;
-      value.second += max;
-      if (value.first > value.second) value.first = value.second;
-      value.first  = tcrop(value.first, range.first, range.second);
-      value.second = tcrop(value.second, range.first, range.second);
-
-      setValue(prop, value);
-    }
-
-  } locals = {this};
-
-  TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5);
-  TRectD invalidateRect(m_brushPos - halfThick, m_brushPos + halfThick);
-
-  if (e.isCtrlPressed() && e.isAltPressed() && !e.isShiftPressed() &&
-      Preferences::instance()->useCtrlAltToResizeBrushEnabled()) {
-    // Resize the brush if CTRL+ALT is pressed and the preference is enabled.
-    const TPointD &diff = pos - m_mousePos;
-    double max          = diff.x / 2;
-    double min          = diff.y / 2;
-
-    locals.addMinMaxSeparate(m_thickness, min, max);
-
-    double radius = m_thickness.getValue().second * 0.5;
-    invalidateRect += TRectD(m_brushPos - TPointD(radius, radius),
-                             m_brushPos + TPointD(radius, radius));
-
-  } else {
-    m_mousePos = pos;
-    m_brushPos = pos;
-
-    TPointD snapThick(6.0 * m_pixelSize, 6.0 * m_pixelSize);
-    // In order to clear the previous snap indicator
-    if (m_foundFirstSnap)
-      invalidateRect +=
-          TRectD(m_firstSnapPoint - snapThick, m_firstSnapPoint + snapThick);
-
-    m_firstSnapPoint = pos;
-    m_foundFirstSnap = false;
-    m_altPressed     = e.isAltPressed() && !e.isCtrlPressed();
-    checkStrokeSnapping(true, m_altPressed);
-    checkGuideSnapping(true, m_altPressed);
-    m_brushPos = m_firstSnapPoint;
-    // In order to draw the snap indicator
-    if (m_foundFirstSnap)
-      invalidateRect +=
-          TRectD(m_firstSnapPoint - snapThick, m_firstSnapPoint + snapThick);
-
-    invalidateRect += TRectD(pos - halfThick, pos + halfThick);
-  }
-
-  invalidate(invalidateRect.enlarge(2));
-
-  if (m_minThick == 0 && m_maxThick == 0) {
-    m_minThick = m_thickness.getValue().first;
-    m_maxThick = m_thickness.getValue().second;
-  }
-}
-
 //-------------------------------------------------------------------------------------------------------------
 
 void ToonzVectorBrushTool::checkStrokeSnapping(bool beforeMousePress,
@@ -1462,6 +1416,8 @@ void ToonzVectorBrushTool::checkGuideSnapping(bool beforeMousePress,
 //-------------------------------------------------------------------------------------------------------------
 
 void ToonzVectorBrushTool::draw() {
+  m_inputmanager.draw();
+  
   /*--ショートカットでのツール切り替え時に赤点が描かれるのを防止する--*/
   if (m_minThick == 0 && m_maxThick == 0 &&
       !Preferences::instance()->getShow0ThickLines())
diff --git a/toonz/sources/tnztools/toonzvectorbrushtool.h b/toonz/sources/tnztools/toonzvectorbrushtool.h
index 7b8bd63..b43d5b1 100644
--- a/toonz/sources/tnztools/toonzvectorbrushtool.h
+++ b/toonz/sources/tnztools/toonzvectorbrushtool.h
@@ -13,6 +13,17 @@
 #include "tools/tool.h"
 #include "tools/cursors.h"
 
+#include <tools/inputmanager.h>
+#include <tools/modifiers/modifierline.h>
+#include <tools/modifiers/modifiertangents.h>
+#include <tools/modifiers/modifierassistants.h>
+#include <tools/modifiers/modifiersegmentation.h>
+#include <tools/modifiers/modifiersimplify.h>
+#include <tools/modifiers/modifiersmooth.h>
+#ifndef NDEBUG
+#include <tools/modifiers/modifiertest.h>
+#endif
+
 #include "toonzrasterbrushtool.h"
 
 #include <QCoreApplication>
@@ -80,7 +91,9 @@ public:
 //    Brush Tool declaration
 //************************************************************************
 
-class ToonzVectorBrushTool final : public TTool {
+class ToonzVectorBrushTool final : public TTool,
+                                   public TInputHandler
+{
   Q_DECLARE_TR_FUNCTIONS(ToonzVectorBrushTool)
 
 public:
@@ -102,6 +115,13 @@ public:
   void mouseMove(const TPointD &pos, const TMouseEvent &e) override;
   bool keyDown(QKeyEvent *event) override;
 
+  void inputMouseMove(const TPointD &position,
+                      const TInputState &state) override;
+  void inputSetBusy(bool busy) override;
+  void inputPaintTracks(const TTrackList &tracks) override;
+  void inputInvalidateRect(const TRectD &bounds) override { invalidate(bounds); }
+  TTool *inputGetTool() override { return this; };
+
   void draw() override;
 
   void onEnter() override;
@@ -143,6 +163,11 @@ public:
                              bool autoGroup = false, bool autoFill = false,
                              bool drawStroke = true);
 
+private:
+  enum MouseEventType { ME_DOWN, ME_DRAG, ME_UP, ME_MOVE };
+  void handleMouseEvent(MouseEventType type, const TPointD &pos,
+                        const TMouseEvent &e);
+
 protected:
   TPropertyGroup m_prop[2];
 
@@ -159,6 +184,18 @@ protected:
   TEnumProperty m_joinStyle;
   TIntProperty m_miterJoinLimit;
 
+  TInputManager m_inputmanager;
+  TSmartPointerT<TModifierLine> m_modifierLine;
+  TSmartPointerT<TModifierTangents> m_modifierTangents;
+  TSmartPointerT<TModifierAssistants> m_modifierAssistants;
+  TSmartPointerT<TModifierSegmentation> m_modifierSegmentation;
+  TSmartPointerT<TModifierSegmentation> m_modifierSmoothSegmentation;
+  TSmartPointerT<TModifierSmooth> m_modifierSmooth[3];
+  TSmartPointerT<TModifierSimplify> m_modifierSimplify;
+#ifndef NDEBUG
+  TSmartPointerT<TModifierTest> m_modifierTest;
+#endif
+
   StrokeGenerator m_track;
   StrokeGenerator m_rangeTrack;
   RasterStrokeGenerator *m_rasterTrack;
@@ -167,7 +204,7 @@ protected:
   TTileSaverCM32 *m_tileSaver;
   TFrameId m_firstFrameId, m_veryFirstFrameId;
   TPixel32 m_currentColor;
-  int m_styleId;
+  int m_styleId; // bwtodo: remove
   double m_minThick, m_maxThick;
 
   // for snapping and framerange
diff --git a/toonz/sources/toonzlib/strokegenerator.cpp b/toonz/sources/toonzlib/strokegenerator.cpp
index 9a5c9c7..755a928 100644
--- a/toonz/sources/toonzlib/strokegenerator.cpp
+++ b/toonz/sources/toonzlib/strokegenerator.cpp
@@ -26,32 +26,61 @@ bool StrokeGenerator::isEmpty() const { return m_points.empty(); }
 
 //-------------------------------------------------------------------
 
-void StrokeGenerator::add(const TThickPoint &point, double pixelSize2) {
+bool StrokeGenerator::add(const TThickPoint &point, double pixelSize2) {
   if (m_points.empty()) {
     double x = point.x, y = point.y, d = point.thick + 3;
     m_points.push_back(point);
     TRectD rect(x - d, y - d, x + d, y + d);
-    m_modifiedRegion     = rect;
-    m_lastPointRect      = rect;
-    m_lastModifiedRegion = rect;
+    m_modifiedRegion     += rect;
+    m_lastPointRect      += rect;
+    m_lastModifiedRegion += rect;
     m_p0 = m_p1 = point;
-  } else {
-    TThickPoint lastPoint = m_points.back();
-    if (tdistance2(lastPoint, point) >= 4 * pixelSize2) {
-      m_points.push_back(point);
+    return true;
+  }
+
+  TThickPoint lastPoint = m_points.back();
+  if (tdistance2(lastPoint, point) >= 4 * pixelSize2) {
+    m_points.push_back(point);
+    double d = std::max(point.thick, lastPoint.thick) + 3;
+    TRectD rect(TRectD(lastPoint, point).enlarge(d));
+    m_modifiedRegion += rect;
+    m_lastModifiedRegion += rect;
+    m_lastPointRect = rect;
+    return true;
+  }
+  
+  m_points.back().thick = std::max(m_points.back().thick, point.thick);
+  return false;
+}
+
+//-------------------------------------------------------------------
+
+void StrokeGenerator::pop() {
+  if (!m_points.empty()) {
+    TRectD rect;
+    TThickPoint point = m_points.back();
+    m_points.pop_back();
+    if (!m_points.empty()) {
+      const TThickPoint &lastPoint = m_points.back();
       double d = std::max(point.thick, lastPoint.thick) + 3;
-      TRectD rect(TRectD(lastPoint, point).enlarge(d));
-      m_modifiedRegion += rect;
-      m_lastModifiedRegion += rect;
-      m_lastPointRect = rect;
+      rect = TRectD(lastPoint, point).enlarge(d);
     } else {
-      m_points.back().thick = std::max(m_points.back().thick, point.thick);
+      double x = point.x, y = point.y, d = point.thick + 3;
+      rect = TRectD(x - d, y - d, x + d, y + d);
     }
+    m_modifiedRegion += rect;
+    m_lastModifiedRegion += rect;
+    m_lastPointRect = rect;
   }
 }
 
 //-------------------------------------------------------------------
 
+void StrokeGenerator::setLoop(bool loop)
+  { m_loop = loop; }
+
+//-------------------------------------------------------------------
+
 void StrokeGenerator::filterPoints() {
   if (m_points.size() < 10) return;
 
@@ -274,7 +303,7 @@ TPointD StrokeGenerator::getFirstPoint() { return m_points[0]; }
 
 //-------------------------------------------------------------------
 
-TStroke *StrokeGenerator::makeStroke(double error, UINT onlyLastPoints) const {
+TStroke *StrokeGenerator::makeStroke(double error, UINT onlyLastPoints, bool useLoop) const {
   if (onlyLastPoints == 0 || onlyLastPoints > m_points.size())
     return TStroke::interpolate(m_points, error);
 
@@ -283,7 +312,9 @@ TStroke *StrokeGenerator::makeStroke(double error, UINT onlyLastPoints) const {
       m_points.begin() + (m_points.size() - onlyLastPoints);
   copy(first, m_points.end(), lastPoints.begin());
 
-  return TStroke::interpolate(lastPoints, error);
+  TStroke *stroke = TStroke::interpolate(lastPoints, error);
+  if (useLoop) stroke->setSelfLoop(m_loop);
+  return stroke;
 }
 
 //-------------------------------------------------------------------