From a8bb4ae6d1763052d4047137700999f364d4cbe9 Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Sat, 19 Aug 2023 23:39:41 +0200 Subject: [PATCH] New MousePointer to change behavior of mouse Initial implementation, mostly replicating GLMixer features. 5 Modes; default, linear, spring, wiggly and metronome. Save in Settings. Selection in Navigation panel. --- rsc/images/icons.dds | Bin 1638528 -> 1638528 bytes src/CMakeLists.txt | 1 + src/ImGuiToolkit.cpp | 35 +++++ src/ImGuiToolkit.h | 1 + src/MousePointer.cpp | 247 +++++++++++++++++++++++++++++++++++ src/MousePointer.h | 106 +++++++++++++++ src/Settings.cpp | 27 +++- src/Settings.h | 6 +- src/UserInterfaceManager.cpp | 141 +++++++++++++++----- src/UserInterfaceManager.h | 1 + 10 files changed, 528 insertions(+), 37 deletions(-) create mode 100644 src/MousePointer.cpp create mode 100644 src/MousePointer.h diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index 80dddc34173b892e053b0c1cd51a37435997ea6b..e0486690f0f36b9e4ed4af0fa5e21f263fddfab5 100644 GIT binary patch delta 3789 zcmZu!3vg6d8UF9x!)Eh<-0W^%BsQ0VN_p4~gGHx>EHbuLsBXov3M~XF!9Ik*h@BBS z*$6aMD$DI!PwNaO3wEG|5|@Wpv>LW_NFTNl3sQ@d4MP9{%a##H$YYQHe|FE^MLlqG z?|1(HeE)a;^ST>ueKd^!H))&ngRXP$*emvUj%)UB|LmITEdVSA1o>{GR75MA_SrA= zx#oMzpc%G70?rC7uVSg?Wy{>3k5)9rc;z*1oPF}&u3}#mT!R}h2t&$Cc*JzkB6^~O zxv@jLKU@QtHLP2ocs4n1f{=m=&iH_S&Seh z+Ql4YF9Zf!o@y&R&yL>7#`;g8L7<_i)x7G;7Rl)D^Gi+R7u z$3ncDHhz$^3vNBn%9qI*lcVcpDVB2eIBK!;T;swHDT%ZJPWr=|n6CmFL(mT~2q-L8 z;nqv#riYdkgF=2^rMAG{7-hHl_?aE0^-aGK3Gr8lwKEtwnap&ODrAOJVu8boxfvBd zmL8mOLq zXVN|~b7>nABU?Xq&v$&MMC)rBcxa^9FoifJ%!@hK2qbmkxj0*ewZcJrTMw)E8FJ=x z`t@j8)9SL|kLZxdwZeh=nxJM|{p?P!-Mq(hRcnh6GOS~;y0I2u^_8?CsMAvv}oIyv99mFG!h+nZ%&yB zmXc^{N2`Y0`FR(MTWuzPXok1+GxMGyD=DL5%su>ZK5l|8nG0*f3On&saUVu;u^U#~ zqJn<9o?s~YPyb|XAFrQT>YwrD+~$LUQ2G=RVWByYFDbEF;RwzL1Bezhy)wa=arDZ5 z5s;@ZX;}BR^p=09Z#sI_Usuuz5om#>IW=3Pno8CmEpK{q)=j)G&q7S`=NMG{5AripSqDx`^S1(I&CPjQ4B-X|OK~z@kyxVEDq%V^bnr-C!nokp z<@ZlbxtI1a>N`>`mp>U{hdC5OVx^S#)fH80m!p$+)s_@M<`N_~6dAVr4iqPhU9kqH zS*;TvHJ>@^Xd#!M+?6+jh&6b^IsC{@g*uQna|EvAfnzExiSTex_-oLmi2^Y_>S;RR zl|5UH!EpKl+LNMVcl3(C^6rSJR_>A3O6L|N@3gp(YVHMRaWcF*=$<5hvg2qr+vmEA zUzaIg!QJ0vdkhmo&NFFqv@>nG3c$;~Wq%5lq7%zkk0` z%V)l!KVr2mrc)pA%k4sA`n;qfNbAyH*(t_T3sJXg)y^2K4Y#>_2TXKBrBB)2r*Vhc zv6alwT0#)G^bQvDZVS3BleH1lw#Zp^Fh2H-hC0Q0} zR{)=cdE;I;<1=6Me+rlAHX{TlgQF+V);mC}RSv4AM@*w$KO-^061 zW|a>4?qGLnOpm?__rNcWAiNnEc|)3sa+Jpxj49W}1N^NIz2ATe4e1h?JvL6~r<>-d#@PIsJ!o2PA;?gg5JNGF?ovEcZDA&L! zeiX&Epu+Ub|LB?n?SS`dA#Sq6xcV()NS^g_hYV!}MyLx3Ou1J9{VJgicxg(Jjx(9C z<5N+fY2smr|CDE1q&w5qPdk%(H&57Xk_P;|&88MCXvUKpfPYw3+G2!NA2_xqJtzbn zkdvP*6I-aA3Pm2yE?0%|x6D$}DZ22B^%cA=!}$G=$5~Q@!yVy4ex}q_QQhwNRz>8t zB1~aX^>I7iAk+FTYjoF&!XAAIE{C7CoQD(t`yqZJ$JE>9ACDVUG5`PXh}=&m@>7LI zfhTn*p4T2~BTPpz6@_uZb@0B+MJ4jI5Y?TUiYDFovOS)ZePm%teVJilw)1*c^Q+Ig z+Ku33>F5wz;R)vQTbmBQ4rTd07ukt}W9u{w9p9B$S>m-ZNS)r+x4s1HSghl)mSQ!q YmSOc`^RugU2saAK> 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 MmYIW3EbQF>e{Re12><{9 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: