diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index 80dddc3..e048669 100644 Binary files a/rsc/images/icons.dds and b/rsc/images/icons.dds differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a4ea4fb..4fb477f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -96,6 +96,7 @@ set(VMIX_SRCS MultiFileRecorder.cpp DisplaysView.cpp ScreenCaptureSource.cpp + MousePointer.cpp ) ##### diff --git a/src/ImGuiToolkit.cpp b/src/ImGuiToolkit.cpp index 06cb712..322c9a3 100644 --- a/src/ImGuiToolkit.cpp +++ b/src/ImGuiToolkit.cpp @@ -437,6 +437,41 @@ bool ImGuiToolkit::IconToggle(int i, int j, int i_toggle, int j_toggle, bool* to } +bool ImGuiToolkit::IconToggle(int i, int j, bool* toggle, const char *tooltip, const char* shortcut) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGui::PushID( i * 20 + j + ( tooltip ? window->GetID(tooltip) : 0) ); + + float frame_height = ImGui::GetFrameHeight(); + ImVec2 draw_pos = ImGui::GetCursorScreenPos(); + + // toggle action : operate on the whole area + bool ret = false; + ImGui::InvisibleButton("##iconijtogglebutton", ImVec2(frame_height, frame_height)); + if (ImGui::IsItemClicked()) { + *toggle = !*toggle; + ret = true; + } + if (tooltip != nullptr && ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip(tooltip, shortcut); + + ImGui::SetCursorScreenPos(draw_pos); + + // draw with hovered color + const ImVec4* colors = ImGui::GetStyle().Colors; + ImGui::PushStyleColor( ImGuiCol_Text, *toggle ? colors[ImGuiCol_DragDropTarget] : colors[ImGuiCol_Text] ); + ImGui::PushStyleColor( ImGuiCol_Text, ImGui::IsItemHovered() ? colors[ImGuiCol_NavHighlight] : colors[ImGuiCol_Text] ); +// ImGui::Text("%s", icon); + Icon(i, j, !ret); + ImGui::PopStyleColor(2); + + ImGui::PopID(); + return ret; +} + bool ImGuiToolkit::IconToggle(const char* icon, bool* toggle, const char *tooltip, const char* shortcut) { bool ret = false; diff --git a/src/ImGuiToolkit.h b/src/ImGuiToolkit.h index 7332881..55fd419 100644 --- a/src/ImGuiToolkit.h +++ b/src/ImGuiToolkit.h @@ -20,6 +20,7 @@ namespace ImGuiToolkit bool IconButton (const char* icon, const char *tooltips = nullptr, const char *shortcut = nullptr); bool IconMultistate (std::vector > icons, int* state, std::vector tooltips); bool IconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltips[] = nullptr); + bool IconToggle (int i, int j, bool* toggle, const char *tooltip = nullptr, const char *shortcut = nullptr); bool IconToggle (const char* icon, bool* toggle, const char *tooltip = nullptr, const char *shortcut = nullptr); void ShowIconsWindow(bool* p_open); diff --git a/src/MousePointer.cpp b/src/MousePointer.cpp new file mode 100644 index 0000000..0a5b586 --- /dev/null +++ b/src/MousePointer.cpp @@ -0,0 +1,247 @@ +/* + * This file is part of vimix - video live mixer + * + * **Copyright** (C) 2019-2023 Bruno Herbelin + * + * This program 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 3 of the License, or + * (at your option) any later version. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +**/ + +#include // for diskRand + +#include "imgui.h" + +#include "Metronome.h" +#include "MousePointer.h" + +std::vector< std::tuple > Pointer::Modes = { + { ICON_POINTER_DEFAULT, std::string("Default") }, + { ICON_POINTER_LINEAR, std::string("Linear") }, + { ICON_POINTER_SPRING, std::string("Spring") }, + { ICON_POINTER_WIGGLY, std::string("Wiggly") }, + { ICON_POINTER_METRONOME, std::string("Metronome") } +}; + +#define POINTER_LINEAR_MIN_SPEED 40.f +#define POINTER_LINEAR_MAX_SPEED 800.f +#define POINTER_LINEAR_THICKNESS 4.f +#define POINTER_LINEAR_ARROW 40.f + +void PointerLinear::update(glm::vec2 pos, float dt) +{ + float speed = POINTER_LINEAR_MIN_SPEED + (POINTER_LINEAR_MAX_SPEED - POINTER_LINEAR_MIN_SPEED) * strength_; + + glm::vec2 delta = pos - pos_ ; + if (glm::length(delta) > 10.f ) + pos_ += glm::normalize(delta) * (speed * dt); +} + +void PointerLinear::draw() +{ + ImGuiIO& io = ImGui::GetIO(); + const glm::vec2 start = glm::vec2( io.MousePos.x, io.MousePos.y); + const glm::vec2 end = glm::vec2( pos_.x / io.DisplayFramebufferScale.x, pos_.y / io.DisplayFramebufferScale.y ); + const ImVec2 _end = ImVec2( end.x, end.y ); + + // draw line + ImGui::GetBackgroundDrawList()->AddLine(io.MousePos, _end, ImGui::GetColorU32(ImGuiCol_HeaderActive), POINTER_LINEAR_THICKNESS); + ImGui::GetBackgroundDrawList()->AddCircleFilled(_end, 6.0, ImGui::GetColorU32(ImGuiCol_HeaderActive)); + + // direction vector + glm::vec2 delta = start - end; + float l = glm::length(delta); + delta = glm::normalize( delta ); + + // draw dots regularly to show speed + for (float p = 0.f; p < l; p += 200.f * (strength_ + 0.1f)) { + glm::vec2 point = start - delta * p; + ImGui::GetBackgroundDrawList()->AddCircleFilled(ImVec2(point.x,point.y), 4.0, ImGui::GetColorU32(ImGuiCol_HeaderActive)); + } + + // draw arrow head + if ( l > POINTER_LINEAR_ARROW * 1.5f) { + glm::vec2 ortho = glm::normalize( glm::vec2( glm::cross( glm::vec3(delta, 0.f), glm::vec3(0.f, 0.f, 1.f)) )); + ortho *= POINTER_LINEAR_ARROW; + delta *= POINTER_LINEAR_ARROW; + const glm::vec2 pointA = start - delta + ortho * 0.5f; + const glm::vec2 pointB = start - delta - ortho * 0.5f; + + ImGui::GetBackgroundDrawList()->AddTriangleFilled(io.MousePos, ImVec2(pointA.x, pointA.y), + ImVec2(pointB.x, pointB.y), ImGui::GetColorU32(ImGuiCol_HeaderActive)); + } +} + +#define POINTER_WIGGLY_MIN_RADIUS 30.f +#define POINTER_WIGGLY_MAX_RADIUS 300.f +#define POINTER_WIGGLY_SMOOTHING 10 + +void PointerWiggly::update(glm::vec2 pos, float) +{ + float radius = POINTER_WIGGLY_MIN_RADIUS + (POINTER_WIGGLY_MAX_RADIUS - POINTER_WIGGLY_MIN_RADIUS) * strength_; + + // change pos to a random point in a close radius + pos += glm::diskRand( radius ); + + // smooth a little and apply + const float emaexp = 2.0 / float( POINTER_WIGGLY_SMOOTHING + 1); + pos_ = emaexp * pos + (1.f - emaexp) * pos_; +} + +void PointerWiggly::draw() +{ + ImGuiIO& io = ImGui::GetIO(); + const ImVec2 _end = ImVec2( pos_.x / io.DisplayFramebufferScale.x, pos_.y / io.DisplayFramebufferScale.y ); + ImGui::GetBackgroundDrawList()->AddLine(io.MousePos, _end, ImGui::GetColorU32(ImGuiCol_HeaderActive), 5.f); + + const float radius = POINTER_WIGGLY_MIN_RADIUS + (POINTER_WIGGLY_MAX_RADIUS - POINTER_WIGGLY_MIN_RADIUS) * strength_; + ImGui::GetBackgroundDrawList()->AddCircle(io.MousePos, radius * 0.5f, + ImGui::GetColorU32(ImGuiCol_HeaderActive), 0, 2.f + 4.f * strength_); +} + +#define POINTER_METRONOME_RADIUS 30.f + +void PointerMetronome::update(glm::vec2 pos, float dt) +{ + if ( Metronome::manager().timeToBeat() < std::chrono::milliseconds( (uint)floor(dt * 1000.f) )) { + pos_ = pos; + } +} + +void PointerMetronome::draw() +{ + ImGuiIO& io = ImGui::GetIO(); + const ImVec2 end = ImVec2(pos_.x / io.DisplayFramebufferScale.x, pos_.y / io.DisplayFramebufferScale.y); + ImGui::GetBackgroundDrawList()->AddLine(io.MousePos, end, ImGui::GetColorU32(ImGuiCol_HeaderActive), 4.f); + ImGui::GetBackgroundDrawList()->AddCircle(io.MousePos, POINTER_METRONOME_RADIUS, ImGui::GetColorU32(ImGuiCol_HeaderActive), 0, 3.f); + ImGui::GetBackgroundDrawList()->AddCircleFilled(end, 6.0, ImGui::GetColorU32(ImGuiCol_HeaderActive)); + + double t = Metronome::manager().phase(); + t -= floor(t); + ImGui::GetBackgroundDrawList()->AddCircleFilled(io.MousePos, t * POINTER_METRONOME_RADIUS, ImGui::GetColorU32(ImGuiCol_HeaderActive), 0); +} + +#define POINTER_SPRING_MIN_MASS 6.f +#define POINTER_SPRING_MAX_MASS 40.f + +void PointerSpring::initiate(glm::vec2 pos) +{ + Pointer::initiate(pos); + velocity_ = glm::vec2(0.f); +} + +void PointerSpring::update(glm::vec2 pos, float dt) +{ + // percentage of loss of energy at every update + const float viscousness = 0.75; + // force applied on the mass, as percent of the Maximum mass + const float stiffness = 0.8; + // damping : opposite direction of force, non proportional to mass + const float damping = 60.0; + // mass as a percentage of min to max + const float mass = POINTER_SPRING_MAX_MASS - (POINTER_SPRING_MAX_MASS - POINTER_SPRING_MIN_MASS) * strength_; + + // compute delta betwen initial and current position + glm::vec2 delta = pos - pos_; + // apply force on velocity : spring stiffness / mass + velocity_ += delta * ( (POINTER_SPRING_MAX_MASS * stiffness) / mass ); + // apply damping dynamics + velocity_ -= damping * dt * glm::normalize(delta); + // compute new position : add velocity x time + pos_ += dt * velocity_; + // diminish velocity by viscousness of substrate + // (loss of energy between updates) + velocity_ *= viscousness; +} + +void PointerSpring::draw() +{ + ImGuiIO& io = ImGui::GetIO(); + glm::vec2 _start = glm::vec2( io.MousePos.x * io.DisplayFramebufferScale.x, io.MousePos.y * io.DisplayFramebufferScale.y ); + + const glm::vec2 delta = pos_ - _start; + glm::vec2 ortho = glm::normalize( glm::vec2( glm::cross( glm::vec3(delta, 0.f), glm::vec3(0.f, 0.f, 1.f)) )); + ortho *= 0.05f * glm::length( velocity_ ); + + // draw a wave with 3 bezier + glm::vec2 _third = _start + delta * 1.f / 9.f + ortho; + glm::vec2 _twothird = _start + delta * 2.f / 9.f - ortho; + glm::vec2 _end = _start + delta * 3.f / 9.f; + + ImVec2 start = ImVec2(_start.x / io.DisplayFramebufferScale.x, _start.y / io.DisplayFramebufferScale.y); + ImVec2 third = ImVec2(_third.x / io.DisplayFramebufferScale.x, _third.y / io.DisplayFramebufferScale.y); + ImVec2 twothird = ImVec2(_twothird.x / io.DisplayFramebufferScale.x, _twothird.y / io.DisplayFramebufferScale.y); + ImVec2 end = ImVec2(_end.x / io.DisplayFramebufferScale.x, _end.y / io.DisplayFramebufferScale.y); + ImGui::GetBackgroundDrawList()->AddBezierCurve(start, third, twothird, end, + ImGui::GetColorU32(ImGuiCol_HeaderActive), 5.f); + _start = _end; + _third = _start + delta * 1.f / 9.f + ortho; + _twothird = _start + delta * 2.f / 9.f - ortho; + _end = _start + delta * 3.f / 9.f; + + start = ImVec2(_start.x / io.DisplayFramebufferScale.x, _start.y / io.DisplayFramebufferScale.y); + third = ImVec2(_third.x / io.DisplayFramebufferScale.x, _third.y / io.DisplayFramebufferScale.y); + twothird = ImVec2(_twothird.x / io.DisplayFramebufferScale.x, _twothird.y / io.DisplayFramebufferScale.y); + end = ImVec2(_end.x / io.DisplayFramebufferScale.x, _end.y / io.DisplayFramebufferScale.y); + ImGui::GetBackgroundDrawList()->AddBezierCurve(start, third, twothird, end, + ImGui::GetColorU32(ImGuiCol_HeaderActive), 5.f); + _start = _end; + _third = _start + delta * 1.f / 9.f + ortho; + _twothird = _start + delta * 2.f / 9.f - ortho; + + start = ImVec2(_start.x / io.DisplayFramebufferScale.x, _start.y / io.DisplayFramebufferScale.y); + third = ImVec2(_third.x / io.DisplayFramebufferScale.x, _third.y / io.DisplayFramebufferScale.y); + twothird = ImVec2(_twothird.x / io.DisplayFramebufferScale.x, _twothird.y / io.DisplayFramebufferScale.y); + end = ImVec2(pos_.x / io.DisplayFramebufferScale.x, pos_.y / io.DisplayFramebufferScale.y); + ImGui::GetBackgroundDrawList()->AddBezierCurve(start, third, twothird, end, + ImGui::GetColorU32(ImGuiCol_HeaderActive), 5.f); + + // represent the weight with a filled circle + float mass = POINTER_SPRING_MAX_MASS - (POINTER_SPRING_MAX_MASS - POINTER_SPRING_MIN_MASS) * strength_; + ImGui::GetBackgroundDrawList()->AddCircleFilled(end, mass, ImGui::GetColorU32(ImGuiCol_HeaderActive)); +} + + +MousePointer::MousePointer() : mode_(Pointer::POINTER_DEFAULT), active_(nullptr) +{ + active_ = new Pointer; +} + +void MousePointer::setActiveMode(Pointer::Mode m) +{ + if (mode_ != m) { + + mode_ = m; + + if (active_) + delete active_; + + switch (mode_) { + case Pointer::POINTER_SPRING: + active_ = new PointerSpring; + break; + case Pointer::POINTER_METRONOME: + active_ = new PointerMetronome; + break; + case Pointer::POINTER_LINEAR: + active_ = new PointerLinear; + break; + case Pointer::POINTER_WIGGLY: + active_ = new PointerWiggly; + break; + default: + case Pointer::POINTER_DEFAULT: + active_ = new Pointer; + break; + } + } +} diff --git a/src/MousePointer.h b/src/MousePointer.h new file mode 100644 index 0000000..c86eed0 --- /dev/null +++ b/src/MousePointer.h @@ -0,0 +1,106 @@ +#ifndef POINTER_H +#define POINTER_H + +#include +#include +#include + +#define ICON_POINTER_DEFAULT 7, 3 +#define ICON_POINTER_OPTION 12, 9 +#define ICON_POINTER_SPRING 13, 9 +#define ICON_POINTER_LINEAR 14, 9 +#define ICON_POINTER_WIGGLY 10, 3 +#define ICON_POINTER_METRONOME 6, 13 + +class Pointer +{ +public: + typedef enum { + POINTER_DEFAULT = 0, + POINTER_LINEAR, + POINTER_SPRING, + POINTER_WIGGLY, + POINTER_METRONOME, + POINTER_INVALID + } Mode; + static std::vector< std::tuple > Modes; + + Pointer() : strength_(0.5) {} + virtual ~Pointer() {} + inline glm::vec2 pos() { return pos_; } + + virtual void initiate(glm::vec2 pos) { pos_ = pos; } + virtual void update(glm::vec2 pos, float) { pos_ = pos; } + virtual void draw() {} + + inline void setStrength(float percent) { strength_ = glm::clamp(percent, 0.f, 1.f); } + inline float strength() const { return strength_; } + +protected: + glm::vec2 pos_; + float strength_; +}; + +class PointerLinear : public Pointer +{ +public: + PointerLinear() {} + void update(glm::vec2 pos, float dt) override; + void draw() override; +}; + +class PointerSpring : public Pointer +{ + glm::vec2 velocity_; +public: + PointerSpring() {} + void initiate(glm::vec2 pos) override; + void update(glm::vec2 pos, float dt) override; + void draw() override; +}; + +class PointerWiggly : public Pointer +{ +public: + PointerWiggly() {} + void update(glm::vec2 pos, float) override; + void draw() override; +}; + +class PointerMetronome : public Pointer +{ +public: + PointerMetronome() {} + void update(glm::vec2 pos, float dt) override; + void draw() override; +}; + + +class MousePointer +{ + // Private Constructor + MousePointer(); + MousePointer(MousePointer const& copy) = delete; + MousePointer& operator=(MousePointer const& copy) = delete; + +public: + + static MousePointer& manager () + { + // The only instance + static MousePointer _instance; + return _instance; + } + + inline Pointer *active() { return active_; } + inline Pointer::Mode activeMode() { return mode_; } + + void setActiveMode(Pointer::Mode m); + +private: + + Pointer::Mode mode_; + Pointer *active_; +}; + +#endif // POINTER_H diff --git a/src/Settings.cpp b/src/Settings.cpp index 6fdca23..c846e30 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -132,7 +132,6 @@ void Settings::Save(uint64_t runtime) applicationNode->SetAttribute("accent_color", application.accent_color); applicationNode->SetAttribute("smooth_transition", application.smooth_transition); applicationNode->SetAttribute("save_snapshot", application.save_version_snapshot); - applicationNode->SetAttribute("smooth_cursor", application.smooth_cursor); applicationNode->SetAttribute("action_history_follow_view", application.action_history_follow_view); applicationNode->SetAttribute("show_tooptips", application.show_tooptips); applicationNode->SetAttribute("accept_connections", application.accept_connections); @@ -214,6 +213,15 @@ void Settings::Save(uint64_t runtime) BrushNode->InsertEndChild( XMLElementFromGLM(&xmlDoc, application.brush) ); pRoot->InsertEndChild(BrushNode); + // Pointer + XMLElement *PointerNode = xmlDoc.NewElement( "MousePointer" ); + PointerNode->SetAttribute("mode", application.mouse_pointer); + for (size_t i = 0; i < application.mouse_pointer_strength.size(); ++i ) { + float v = application.mouse_pointer_strength[i]; + PointerNode->InsertEndChild( XMLElementFromGLM(&xmlDoc, glm::vec2((float)i, v)) ); + } + pRoot->InsertEndChild(PointerNode); + // bloc views { XMLElement *viewsNode = xmlDoc.NewElement( "Views" ); @@ -284,8 +292,6 @@ void Settings::Save(uint64_t runtime) // recent SRT hosts knownhosts->InsertEndChild( save_knownhost(application.recentSRT, "SRT", xmlDoc)); - - pRoot->InsertEndChild(knownhosts); } @@ -410,7 +416,6 @@ void Settings::Load() applicationNode->QueryIntAttribute("accent_color", &application.accent_color); applicationNode->QueryBoolAttribute("smooth_transition", &application.smooth_transition); applicationNode->QueryBoolAttribute("save_snapshot", &application.save_version_snapshot); - applicationNode->QueryBoolAttribute("smooth_cursor", &application.smooth_cursor); applicationNode->QueryBoolAttribute("action_history_follow_view", &application.action_history_follow_view); applicationNode->QueryBoolAttribute("show_tooptips", &application.show_tooptips); applicationNode->QueryBoolAttribute("accept_connections", &application.accept_connections); @@ -556,6 +561,20 @@ void Settings::Load() tinyxml2::XMLElementToGLM( brushnode->FirstChildElement("vec3"), application.brush); } + // Pointer + XMLElement * pointernode = pRoot->FirstChildElement("MousePointer"); + if (pointernode != nullptr) { + pointernode->QueryIntAttribute("mode", &application.mouse_pointer); + + XMLElement* strengthNode = pointernode->FirstChildElement("vec2"); + for( ; strengthNode ; strengthNode = strengthNode->NextSiblingElement()) + { + glm::vec2 val; + tinyxml2::XMLElementToGLM( strengthNode, val); + application.mouse_pointer_strength[ (size_t) ceil(val.x) ] = val.y; + } + } + // bloc views { XMLElement * pElement = pRoot->FirstChildElement("Views"); diff --git a/src/Settings.h b/src/Settings.h index 21be66a..d31962f 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -271,7 +271,8 @@ struct Application int accent_color; bool save_version_snapshot; bool smooth_transition; - bool smooth_cursor; + int mouse_pointer; + std::vector mouse_pointer_strength; bool action_history_follow_view; bool show_tooptips; @@ -336,7 +337,8 @@ struct Application accent_color = 0; smooth_transition = false; save_version_snapshot = false; - smooth_cursor = false; + mouse_pointer = 0; + mouse_pointer_strength = {0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f}; action_history_follow_view = false; show_tooptips = true; accept_connections = false; diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp index 0599181..0b4acd0 100644 --- a/src/UserInterfaceManager.cpp +++ b/src/UserInterfaceManager.cpp @@ -76,6 +76,7 @@ #include "ShmdataBroadcast.h" #include "VideoBroadcast.h" #include "MultiFileRecorder.h" +#include "MousePointer.h" #include "UserInterfaceManager.h" @@ -442,13 +443,10 @@ void UserInterface::handleKeyboard() void UserInterface::handleMouse() { - ImGuiIO& io = ImGui::GetIO(); glm::vec2 mousepos(io.MousePos.x * io.DisplayFramebufferScale.x, io.MousePos.y * io.DisplayFramebufferScale.y); mousepos = glm::clamp(mousepos, glm::vec2(0.f), glm::vec2(io.DisplaySize.x * io.DisplayFramebufferScale.x, io.DisplaySize.y * io.DisplayFramebufferScale.y)); - static glm::vec2 mouse_smooth = mousepos; - static glm::vec2 mouseclic[2]; mouseclic[ImGuiMouseButton_Left] = glm::vec2(io.MouseClickedPos[ImGuiMouseButton_Left].x * io.DisplayFramebufferScale.y, io.MouseClickedPos[ImGuiMouseButton_Left].y* io.DisplayFramebufferScale.x); mouseclic[ImGuiMouseButton_Right] = glm::vec2(io.MouseClickedPos[ImGuiMouseButton_Right].x * io.DisplayFramebufferScale.y, io.MouseClickedPos[ImGuiMouseButton_Right].y* io.DisplayFramebufferScale.x); @@ -474,15 +472,6 @@ void UserInterface::handleMouse() // if not on any window if ( !ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) && !ImGui::IsWindowFocused(ImGuiHoveredFlags_AnyWindow) ) { - // - // Mouse wheel over background - // - if ( io.MouseWheel != 0) { - // scroll => zoom current view - Mixer::manager().view()->zoom( io.MouseWheel ); - } - // TODO : zoom with center on source if over current - // // RIGHT Mouse button // @@ -512,7 +501,11 @@ void UserInterface::handleMouse() if ( !mousedown ) { mousedown = true; - mouse_smooth = mousepos; + + // initiate Mouse pointer from position at mouse down event + MousePointer::manager().setActiveMode( (Pointer::Mode) Settings::application.mouse_pointer ); + MousePointer::manager().active()->setStrength( Settings::application.mouse_pointer_strength[Settings::application.mouse_pointer] ); + MousePointer::manager().active()->initiate(mousepos); // ask the view what was picked picked = Mixer::manager().view()->pick(mousepos); @@ -592,17 +585,8 @@ void UserInterface::handleMouse() if (view_drag == Mixer::manager().view()) { if ( picked.first != nullptr ) { - // Smooth cursor - if (Settings::application.smooth_cursor) { - // TODO : physics implementation - float smoothing = 10.f / ( MAX(io.Framerate, 1.f) ); - glm::vec2 d = mousepos - mouse_smooth; - mouse_smooth += smoothing * d; - ImVec2 start = ImVec2(mouse_smooth.x / io.DisplayFramebufferScale.x, mouse_smooth.y / io.DisplayFramebufferScale.y); - ImGui::GetBackgroundDrawList()->AddLine(io.MousePos, start, ImGui::GetColorU32(ImGuiCol_HeaderActive), 5.f); - } - else - mouse_smooth = mousepos; + // Apply Mouse pointer filter + MousePointer::manager().active()->update(mousepos, 1.f / ( MAX(io.Framerate, 1.f) )); // action on current source Source *current = Mixer::manager().currentSource(); @@ -612,19 +596,32 @@ void UserInterface::handleMouse() // grab others from selection for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) { if ( *it != current && !(*it)->locked() ) - Mixer::manager().view()->grab(*it, mouseclic[ImGuiMouseButton_Left], mouse_smooth, picked); + Mixer::manager().view()->grab(*it, mouseclic[ImGuiMouseButton_Left], + MousePointer::manager().active()->pos(), picked); } } // grab current sources - View::Cursor c = Mixer::manager().view()->grab(current, mouseclic[ImGuiMouseButton_Left], mouse_smooth, picked); + View::Cursor c = Mixer::manager().view()->grab(current, mouseclic[ImGuiMouseButton_Left], + MousePointer::manager().active()->pos(), picked); SetMouseCursor(io.MousePos, c); + + // scrollwheel changes strength of Mouse Pointer + if ( io.MouseWheel != 0) { + MousePointer::manager().active()->setStrength( MousePointer::manager().active()->strength() + 0.1 * io.MouseWheel); + Settings::application.mouse_pointer_strength[Settings::application.mouse_pointer] = MousePointer::manager().active()->strength(); + } + + // Draw Mouse pointer effect + MousePointer::manager().active()->draw(); } // action on other (non-source) elements in the view else { - View::Cursor c = Mixer::manager().view()->grab(nullptr, mouseclic[ImGuiMouseButton_Left], mouse_smooth, picked); + View::Cursor c = Mixer::manager().view()->grab(nullptr, mouseclic[ImGuiMouseButton_Left], mousepos, picked); SetMouseCursor(io.MousePos, c); } + + } // Selection area else { @@ -640,6 +637,13 @@ void UserInterface::handleMouse() } } + // + // Mouse wheel over background without source action + // + else if ( !mousedown && io.MouseWheel != 0) { + // scroll => zoom current view + Mixer::manager().view()->zoom( io.MouseWheel ); + } } else { // cancel all operations on view when interacting on GUI @@ -2770,7 +2774,7 @@ void Navigator::Render() height_ = ImGui::GetIO().DisplaySize.y; // cover vertically const float icon_width = width_ - 2.f * style.WindowPadding.x; // icons keep padding const ImVec2 iconsize(icon_width, icon_width); - const float sourcelist_height = height_ - 5.5f * icon_width - 5.f * style.WindowPadding.y; // space for 4 icons of view + const float sourcelist_height = height_ - 6.5f * icon_width - 6.f * style.WindowPadding.y; // space for 4 icons of view // hack to show more sources if not enough space; make source icons smaller... ImVec2 sourceiconsize(icon_width, icon_width); @@ -2886,6 +2890,10 @@ void Navigator::Render() if (ImGui::Begin("##navigatorViews", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoScrollWithMouse)) { + // Mouse pointer selector + RenderMousePointerSelector(iconsize); + + // List of icons for View selection bool selected_view[View::INVALID] = { }; selected_view[ Settings::application.current_view ] = true; int previous_view = Settings::application.current_view; @@ -4606,6 +4614,80 @@ void Navigator::RenderMainPannelVimix() ImGui::EndPopup(); } + +} + + +void Navigator::RenderMousePointerSelector(const ImVec2 &size) +{ + ImGuiContext& g = *GImGui; + ImVec2 top = ImGui::GetCursorPos(); + + /// + /// interactive button of the given size: show menu if clic or mouse over + /// + static uint counter_menu_timeout = 0; + if ( ImGui::InvisibleButton("##MenuMousePointerButton", size) || ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup) ) { + + counter_menu_timeout=0; + ImGui::OpenPopup( "MenuMousePointer" ); + } + ImVec2 bottom = ImGui::GetCursorScreenPos(); + + // Change color of icons depending on context menu status + const ImVec4* colors = ImGui::GetStyle().Colors; + ImGui::PushStyleColor( ImGuiCol_Text, ImGui::IsPopupOpen("MenuMousePointer") ? colors[ImGuiCol_DragDropTarget] : colors[ImGuiCol_Text] ); + + // Draw centered icon of Mouse pointer + ImVec2 margin = (size - ImVec2(g.FontSize, g.FontSize)) * 0.42f; + ImGui::SetCursorPos( top + margin ); + + if ( Settings::application.mouse_pointer > 0 ) { + // icon with corner erased + ImGuiToolkit::Icon(ICON_POINTER_OPTION); + + // Draw sub-icon of Mouse pointer type + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT); + ImVec2 t = top + size - ImVec2(g.FontSize, g.FontSize) - ImVec2(g.Style.FramePadding.y, g.Style.FramePadding.y); + ImGui::SetCursorPos( t ); + std::tuple mode = Pointer::Modes.at( (size_t) Settings::application.mouse_pointer); + ImGuiToolkit::Icon(std::get<0>(mode), std::get<1>(mode)); + ImGui::PopFont(); + } + else + // standard icon + ImGuiToolkit::Icon(ICON_POINTER_DEFAULT); + + // Revert + ImGui::PopStyleColor(1); + ImGui::SetCursorScreenPos(bottom); + + /// + /// Render the Popup menu selector + /// + ImGui::SetNextWindowPos( bottom + ImVec2(size.x + g.Style.WindowPadding.x, -size.y), ImGuiCond_Always ); + if (ImGui::BeginPopup( "MenuMousePointer" )) + { + // loop over all mouse pointer modes + for ( size_t m = Pointer::POINTER_DEFAULT; m < Pointer::POINTER_INVALID; ++m) { + bool on = m == (size_t) Settings::application.mouse_pointer; + std::tuple mode = Pointer::Modes.at(m); + // show icon of mouse mode and set mouse pointer if selected + if (ImGuiToolkit::IconToggle( std::get<0>(mode), std::get<1>(mode), &on, std::get<2>(mode).c_str()) ) + Settings::application.mouse_pointer = (int) m; + // space between icons + ImGui::SameLine(0, IMGUI_SAME_LINE); + } + + // timer to close menu like a tooltip + if (ImGui::IsWindowHovered()) + counter_menu_timeout=0; + else if (++counter_menu_timeout > 10) + ImGui::CloseCurrentPopup(); + + ImGui::EndPopup(); + } + } void Navigator::RenderMainPannelSettings() @@ -4637,9 +4719,6 @@ void Navigator::RenderMainPannelSettings() Settings::application.scale = CLAMP(Settings::application.scale, 0.5f, 2.f); ImGui::GetIO().FontGlobalScale = Settings::application.scale; } - ImGuiToolkit::HelpToolTip("Cursor filter that makes movement smoother when manipulating a source."); - ImGui::SameLine(); - ImGuiToolkit::ButtonSwitch( ICON_FA_MOUSE_POINTER " Smooth cursor", &Settings::application.smooth_cursor); // // Recording preferences diff --git a/src/UserInterfaceManager.h b/src/UserInterfaceManager.h index da144ca..4ae0a7c 100644 --- a/src/UserInterfaceManager.h +++ b/src/UserInterfaceManager.h @@ -70,6 +70,7 @@ class Navigator void RenderTransitionPannel(); void RenderNewPannel(); void RenderViewPannel(ImVec2 draw_pos, ImVec2 draw_size); + void RenderMousePointerSelector(const ImVec2 &size); public: