mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-19 22:30:05 +01:00
New ALT key selects alternative mouse Pointer
Maintain ALT to activate the selected mouse Pointer. Also possible to ALT LOCK for maintaining the cursor. Local popup window allows selecting. Changed the View options selection to match this popup approach.
This commit is contained in:
@@ -116,20 +116,6 @@ void DrawVisitor::visit(Switch &n)
|
||||
modelview_ = mv;
|
||||
}
|
||||
|
||||
void DrawVisitor::visit(Primitive &)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
ColorVisitor::ColorVisitor(glm::vec4 color): color_(color)
|
||||
{
|
||||
|
||||
}
|
||||
void ColorVisitor::visit(Node &)
|
||||
{
|
||||
}
|
||||
|
||||
void ColorVisitor::visit(Group &n)
|
||||
{
|
||||
// traverse children
|
||||
@@ -180,6 +166,30 @@ void ColorVisitor::visit(Character &d)
|
||||
d.color = color_;
|
||||
}
|
||||
|
||||
void VisibleVisitor::visit(Node &n)
|
||||
{
|
||||
n.visible_ = visible_;
|
||||
}
|
||||
|
||||
void VisibleVisitor::visit(Group &n)
|
||||
{
|
||||
// traverse children
|
||||
for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) {
|
||||
(*node)->accept(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void VisibleVisitor::visit(Scene &n)
|
||||
{
|
||||
n.root()->accept(*this);
|
||||
}
|
||||
|
||||
void VisibleVisitor::visit(Switch &n)
|
||||
{
|
||||
for (uint c = 0; c < n.numChildren(); ++c) {
|
||||
n.child(c)->accept(*this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ public:
|
||||
|
||||
void visit(Scene& n) override;
|
||||
void visit(Node& n) override;
|
||||
void visit(Primitive& ) override;
|
||||
void visit(Group& n) override;
|
||||
void visit(Switch& n) override;
|
||||
void visit(Primitive& ) override {}
|
||||
};
|
||||
|
||||
|
||||
@@ -44,10 +44,10 @@ class ColorVisitor : public Visitor
|
||||
glm::vec4 color_;
|
||||
|
||||
public:
|
||||
ColorVisitor(glm::vec4 color);
|
||||
ColorVisitor(glm::vec4 color) : color_(color) {}
|
||||
|
||||
void visit(Node&) override {}
|
||||
void visit(Scene& n) override;
|
||||
void visit(Node& n) override;
|
||||
void visit(Group& n) override;
|
||||
void visit(Switch& n) override;
|
||||
|
||||
@@ -59,5 +59,21 @@ public:
|
||||
void visit(Character& ) override;
|
||||
};
|
||||
|
||||
///
|
||||
/// \brief The VisibleVisitor changes the visible flag of
|
||||
/// all nodes to the given value
|
||||
///
|
||||
class VisibleVisitor : public Visitor
|
||||
{
|
||||
bool visible_;
|
||||
|
||||
public:
|
||||
VisibleVisitor(bool visible) : visible_(visible) {}
|
||||
|
||||
void visit(Scene& n) override;
|
||||
void visit(Node& n) override;
|
||||
void visit(Group& n) override;
|
||||
void visit(Switch& n) override;
|
||||
};
|
||||
|
||||
#endif // DRAWVISITOR_H
|
||||
|
||||
@@ -1238,7 +1238,6 @@ void GeometryView::terminate(bool force)
|
||||
(*sit)->handles_[mode_][Handles::ROTATE]->visible_ = true;
|
||||
(*sit)->handles_[mode_][Handles::CROP]->visible_ = true;
|
||||
(*sit)->handles_[mode_][Handles::MENU]->visible_ = true;
|
||||
|
||||
}
|
||||
|
||||
overlay_selection_active_ = false;
|
||||
@@ -1250,70 +1249,100 @@ void GeometryView::terminate(bool force)
|
||||
void GeometryView::arrow (glm::vec2 movement)
|
||||
{
|
||||
static float accumulator = 0.f;
|
||||
accumulator += dt_;
|
||||
accumulator += dt_ * 0.2f;
|
||||
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(movement, scene.root()->transform_);
|
||||
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
Source *current = Mixer::manager().currentSource();
|
||||
|
||||
bool first = true;
|
||||
glm::vec3 delta_translation(0.f);
|
||||
for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
|
||||
if (!current && !Mixer::selection().empty())
|
||||
Mixer::manager().setCurrentSource( Mixer::selection().back() );
|
||||
|
||||
if (current) {
|
||||
|
||||
Group *sourceNode = (*it)->group(mode_);
|
||||
glm::vec3 dest_translation(0.f);
|
||||
if (current_action_ongoing_) {
|
||||
|
||||
if (first) {
|
||||
// dest starts at current
|
||||
dest_translation = sourceNode->translation_;
|
||||
glm::vec2 Position_from = glm::vec2( Rendering::manager().project(current->stored_status_->translation_, scene.root()->transform_) );
|
||||
glm::vec2 Position_to = Position_from + movement * accumulator;
|
||||
|
||||
grab(current, Position_from, Position_to, std::make_pair(current->group(mode_), glm::vec2(0.f) ) );
|
||||
|
||||
// + ALT : discrete displacement
|
||||
if (UserInterface::manager().altModifier()) {
|
||||
if (accumulator > 100.f) {
|
||||
// precise movement with SHIFT
|
||||
if ( UserInterface::manager().shiftModifier() ) {
|
||||
dest_translation += glm::sign(gl_delta) * 0.0011f;
|
||||
dest_translation.x = ROUND(dest_translation.x, 1000.f);
|
||||
dest_translation.y = ROUND(dest_translation.y, 1000.f);
|
||||
}
|
||||
else {
|
||||
dest_translation += glm::sign(gl_delta) * 0.11f;
|
||||
dest_translation.x = ROUND(dest_translation.x, 10.f);
|
||||
dest_translation.y = ROUND(dest_translation.y, 10.f);
|
||||
}
|
||||
|
||||
initiate();
|
||||
accumulator = 0.f;
|
||||
|
||||
adaptGridToSource(current);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// normal case: dest += delta
|
||||
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
|
||||
accumulator = 0.f;
|
||||
}
|
||||
terminate(true);
|
||||
|
||||
// store action in history
|
||||
std::ostringstream info;
|
||||
info << "Position " << std::fixed << std::setprecision(3) << sourceNode->translation_.x;
|
||||
info << ", " << sourceNode->translation_.y ;
|
||||
current_action_ = (*it)->name() + ": " + info.str();
|
||||
|
||||
// delta for others to follow
|
||||
delta_translation = dest_translation - sourceNode->translation_;
|
||||
}
|
||||
else {
|
||||
// dest = current + delta from first
|
||||
dest_translation = sourceNode->translation_ + delta_translation;
|
||||
}
|
||||
|
||||
// apply & request update
|
||||
sourceNode->translation_ = dest_translation;
|
||||
(*it)->touch();
|
||||
// glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
// glm::vec3 gl_Position_to = Rendering::manager().unProject(movement, scene.root()->transform_);
|
||||
// glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
|
||||
first = false;
|
||||
}
|
||||
// bool first = true;
|
||||
// glm::vec3 delta_translation(0.f);
|
||||
// for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
|
||||
|
||||
|
||||
// Group *sourceNode = (*it)->group(mode_);
|
||||
// glm::vec3 dest_translation(0.f);
|
||||
|
||||
// if (first) {
|
||||
// // dest starts at current
|
||||
// dest_translation = sourceNode->translation_;
|
||||
|
||||
// // + ALT : discrete displacement
|
||||
// if (UserInterface::manager().altModifier()) {
|
||||
// if (accumulator > 100.f) {
|
||||
// // precise movement with SHIFT
|
||||
// if ( UserInterface::manager().shiftModifier() ) {
|
||||
// dest_translation += glm::sign(gl_delta) * 0.0011f;
|
||||
// dest_translation.x = ROUND(dest_translation.x, 1000.f);
|
||||
// dest_translation.y = ROUND(dest_translation.y, 1000.f);
|
||||
// }
|
||||
// else {
|
||||
// dest_translation += glm::sign(gl_delta) * 0.11f;
|
||||
// dest_translation.x = ROUND(dest_translation.x, 10.f);
|
||||
// dest_translation.y = ROUND(dest_translation.y, 10.f);
|
||||
// }
|
||||
// accumulator = 0.f;
|
||||
// }
|
||||
// else
|
||||
// break;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // normal case: dest += delta
|
||||
// dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
|
||||
// accumulator = 0.f;
|
||||
// }
|
||||
|
||||
// // store action in history
|
||||
// std::ostringstream info;
|
||||
// info << "Position " << std::fixed << std::setprecision(3) << sourceNode->translation_.x;
|
||||
// info << ", " << sourceNode->translation_.y ;
|
||||
// current_action_ = (*it)->name() + ": " + info.str();
|
||||
|
||||
// // delta for others to follow
|
||||
// delta_translation = dest_translation - sourceNode->translation_;
|
||||
// }
|
||||
// else {
|
||||
// // dest = current + delta from first
|
||||
// dest_translation = sourceNode->translation_ + delta_translation;
|
||||
// }
|
||||
|
||||
// // apply & request update
|
||||
// sourceNode->translation_ = dest_translation;
|
||||
// (*it)->touch();
|
||||
|
||||
// first = false;
|
||||
// }
|
||||
}
|
||||
|
||||
void GeometryView::updateSelectionOverlay(glm::vec4 color)
|
||||
|
||||
@@ -386,62 +386,62 @@ View::Cursor LayerView::over (glm::vec2 pos)
|
||||
|
||||
void LayerView::arrow (glm::vec2 movement)
|
||||
{
|
||||
static float accumulator = 0.f;
|
||||
accumulator += dt_;
|
||||
// static float accumulator = 0.f;
|
||||
// accumulator += dt_;
|
||||
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(glm::vec2(movement.x-movement.y, 0.f), scene.root()->transform_);
|
||||
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
// glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
// glm::vec3 gl_Position_to = Rendering::manager().unProject(glm::vec2(movement.x-movement.y, 0.f), scene.root()->transform_);
|
||||
// glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
|
||||
bool first = true;
|
||||
glm::vec3 delta_translation(0.f);
|
||||
for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) {
|
||||
// bool first = true;
|
||||
// glm::vec3 delta_translation(0.f);
|
||||
// for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) {
|
||||
|
||||
// individual move with SHIFT
|
||||
if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() )
|
||||
continue;
|
||||
// // individual move with SHIFT
|
||||
// if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() )
|
||||
// continue;
|
||||
|
||||
Group *sourceNode = (*it)->group(mode_);
|
||||
glm::vec3 dest_translation(0.f);
|
||||
// Group *sourceNode = (*it)->group(mode_);
|
||||
// glm::vec3 dest_translation(0.f);
|
||||
|
||||
if (first) {
|
||||
// dest starts at current
|
||||
dest_translation = sourceNode->translation_;
|
||||
// if (first) {
|
||||
// // dest starts at current
|
||||
// dest_translation = sourceNode->translation_;
|
||||
|
||||
// + ALT : discrete displacement
|
||||
if (UserInterface::manager().altModifier()) {
|
||||
if (accumulator > 100.f) {
|
||||
dest_translation += glm::sign(gl_delta) * 0.21f;
|
||||
dest_translation.x = ROUND(dest_translation.x, 10.f);
|
||||
accumulator = 0.f;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// normal case: dest += delta
|
||||
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
|
||||
accumulator = 0.f;
|
||||
}
|
||||
// // + ALT : discrete displacement
|
||||
// if (UserInterface::manager().altModifier()) {
|
||||
// if (accumulator > 100.f) {
|
||||
// dest_translation += glm::sign(gl_delta) * 0.21f;
|
||||
// dest_translation.x = ROUND(dest_translation.x, 10.f);
|
||||
// accumulator = 0.f;
|
||||
// }
|
||||
// else
|
||||
// break;
|
||||
// }
|
||||
// else {
|
||||
// // normal case: dest += delta
|
||||
// dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
|
||||
// accumulator = 0.f;
|
||||
// }
|
||||
|
||||
// store action in history
|
||||
std::ostringstream info;
|
||||
info << "Depth " << std::fixed << std::setprecision(2) << (*it)->depth() << " ";
|
||||
current_action_ = (*it)->name() + ": " + info.str();
|
||||
// // store action in history
|
||||
// std::ostringstream info;
|
||||
// info << "Depth " << std::fixed << std::setprecision(2) << (*it)->depth() << " ";
|
||||
// current_action_ = (*it)->name() + ": " + info.str();
|
||||
|
||||
// delta for others to follow
|
||||
delta_translation = dest_translation - sourceNode->translation_;
|
||||
}
|
||||
else {
|
||||
// dest = current + delta from first
|
||||
dest_translation = sourceNode->translation_ + delta_translation;
|
||||
}
|
||||
// // delta for others to follow
|
||||
// delta_translation = dest_translation - sourceNode->translation_;
|
||||
// }
|
||||
// else {
|
||||
// // dest = current + delta from first
|
||||
// dest_translation = sourceNode->translation_ + delta_translation;
|
||||
// }
|
||||
|
||||
// apply & request update
|
||||
setDepth( *it, MAX( -dest_translation.x, 0.f) );
|
||||
// // apply & request update
|
||||
// setDepth( *it, MAX( -dest_translation.x, 0.f) );
|
||||
|
||||
first = false;
|
||||
}
|
||||
// first = false;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -683,68 +683,98 @@ View::Cursor MixingView::over (glm::vec2 pos)
|
||||
void MixingView::arrow (glm::vec2 movement)
|
||||
{
|
||||
static float accumulator = 0.f;
|
||||
accumulator += dt_;
|
||||
accumulator += dt_ * 0.2;
|
||||
Source *current = Mixer::manager().currentSource();
|
||||
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(movement, scene.root()->transform_);
|
||||
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
if (!current && !Mixer::selection().empty())
|
||||
Mixer::manager().setCurrentSource( Mixer::selection().back() );
|
||||
|
||||
bool first = true;
|
||||
glm::vec3 delta_translation(0.f);
|
||||
for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
|
||||
if (current) {
|
||||
|
||||
// individual move with SHIFT
|
||||
if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() )
|
||||
continue;
|
||||
if (current_action_ongoing_) {
|
||||
|
||||
Group *sourceNode = (*it)->group(mode_);
|
||||
glm::vec3 dest_translation(0.f);
|
||||
glm::vec2 Position_from = glm::vec2( Rendering::manager().project(current->stored_status_->translation_, scene.root()->transform_) );
|
||||
glm::vec2 Position_to = Position_from + movement * accumulator;
|
||||
|
||||
if (first) {
|
||||
// dest starts at current
|
||||
dest_translation = sourceNode->translation_;
|
||||
if ( current->mixinggroup_ != nullptr )
|
||||
current->mixinggroup_->setAction( MixingGroup::ACTION_GRAB_ALL );
|
||||
|
||||
grab(current, Position_from, Position_to, std::make_pair(current->group(mode_), glm::vec2(0.f) ) );
|
||||
|
||||
// + ALT : discrete displacement
|
||||
if (UserInterface::manager().altModifier()) {
|
||||
if (accumulator > 100.f) {
|
||||
dest_translation += glm::sign(gl_delta) * 0.1f;
|
||||
dest_translation.x = ROUND(dest_translation.x, 10.f);
|
||||
dest_translation.y = ROUND(dest_translation.y, 10.f);
|
||||
accumulator = 0.f;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// normal case: dest += delta
|
||||
dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
|
||||
|
||||
initiate();
|
||||
accumulator = 0.f;
|
||||
}
|
||||
|
||||
// store action in history
|
||||
std::ostringstream info;
|
||||
if ((*it)->active()) {
|
||||
info << "Alpha " << std::fixed << std::setprecision(3) << (*it)->blendingShader()->color.a << " ";
|
||||
info << ( ((*it)->blendingShader()->color.a > 0.f) ? ICON_FA_EYE : ICON_FA_EYE_SLASH);
|
||||
|
||||
}
|
||||
else
|
||||
info << "Inactive " << ICON_FA_SNOWFLAKE;
|
||||
current_action_ = (*it)->name() + ": " + info.str();
|
||||
terminate(true);
|
||||
|
||||
// delta for others to follow
|
||||
delta_translation = dest_translation - sourceNode->translation_;
|
||||
}
|
||||
else {
|
||||
// dest = current + delta from first
|
||||
dest_translation = sourceNode->translation_ + delta_translation;
|
||||
}
|
||||
|
||||
// apply & request update
|
||||
sourceNode->translation_ = dest_translation;
|
||||
(*it)->touch();
|
||||
// glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
// glm::vec3 gl_Position_to = Rendering::manager().unProject(movement, scene.root()->transform_);
|
||||
// glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
// bool first = true;
|
||||
// glm::vec3 delta_translation(0.f);
|
||||
// for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
|
||||
|
||||
// // individual move with SHIFT
|
||||
// if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() )
|
||||
// continue;
|
||||
|
||||
// Group *sourceNode = (*it)->group(mode_);
|
||||
// glm::vec3 dest_translation(0.f);
|
||||
|
||||
// if (first) {
|
||||
// // dest starts at current
|
||||
// dest_translation = sourceNode->translation_;
|
||||
|
||||
// // + ALT : discrete displacement
|
||||
// if (UserInterface::manager().altModifier()) {
|
||||
// if (accumulator > 100.f) {
|
||||
// dest_translation += glm::sign(gl_delta) * 0.1f;
|
||||
// dest_translation.x = ROUND(dest_translation.x, 10.f);
|
||||
// dest_translation.y = ROUND(dest_translation.y, 10.f);
|
||||
// accumulator = 0.f;
|
||||
// }
|
||||
// else
|
||||
// break;
|
||||
// }
|
||||
// else {
|
||||
// // normal case: dest += delta
|
||||
// dest_translation += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
|
||||
// accumulator = 0.f;
|
||||
// }
|
||||
|
||||
// // store action in history
|
||||
// std::ostringstream info;
|
||||
// if ((*it)->active()) {
|
||||
// info << "Alpha " << std::fixed << std::setprecision(3) << (*it)->blendingShader()->color.a << " ";
|
||||
// info << ( ((*it)->blendingShader()->color.a > 0.f) ? ICON_FA_EYE : ICON_FA_EYE_SLASH);
|
||||
// }
|
||||
// else
|
||||
// info << "Inactive " << ICON_FA_SNOWFLAKE;
|
||||
// current_action_ = (*it)->name() + ": " + info.str();
|
||||
|
||||
// // delta for others to follow
|
||||
// delta_translation = dest_translation - sourceNode->translation_;
|
||||
// }
|
||||
// else {
|
||||
// // dest = current + delta from first
|
||||
// dest_translation = sourceNode->translation_ + delta_translation;
|
||||
// }
|
||||
|
||||
// // apply & request update
|
||||
// sourceNode->translation_ = dest_translation;
|
||||
// (*it)->touch();
|
||||
|
||||
// first = false;
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -216,6 +216,7 @@ void Settings::Save(uint64_t runtime)
|
||||
// Pointer
|
||||
XMLElement *PointerNode = xmlDoc.NewElement( "MousePointer" );
|
||||
PointerNode->SetAttribute("mode", application.mouse_pointer);
|
||||
PointerNode->SetAttribute("lock", application.mouse_pointer_lock);
|
||||
PointerNode->SetAttribute("proportional_grid", application.proportional_grid);
|
||||
for (size_t i = 0; i < application.mouse_pointer_strength.size(); ++i ) {
|
||||
float v = application.mouse_pointer_strength[i];
|
||||
@@ -566,6 +567,7 @@ void Settings::Load()
|
||||
XMLElement * pointernode = pRoot->FirstChildElement("MousePointer");
|
||||
if (pointernode != nullptr) {
|
||||
pointernode->QueryIntAttribute("mode", &application.mouse_pointer);
|
||||
pointernode->QueryBoolAttribute("lock", &application.mouse_pointer_lock);
|
||||
pointernode->QueryBoolAttribute("proportional_grid", &application.proportional_grid);
|
||||
|
||||
XMLElement* strengthNode = pointernode->FirstChildElement("vec2");
|
||||
|
||||
@@ -273,6 +273,7 @@ struct Application
|
||||
bool smooth_transition;
|
||||
bool proportional_grid;
|
||||
int mouse_pointer;
|
||||
bool mouse_pointer_lock;
|
||||
std::vector<float> mouse_pointer_strength;
|
||||
bool action_history_follow_view;
|
||||
bool show_tooptips;
|
||||
@@ -339,7 +340,8 @@ struct Application
|
||||
smooth_transition = false;
|
||||
save_version_snapshot = false;
|
||||
proportional_grid = true;
|
||||
mouse_pointer = 0;
|
||||
mouse_pointer = 1;
|
||||
mouse_pointer_lock = false;
|
||||
mouse_pointer_strength = {0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f};
|
||||
action_history_follow_view = false;
|
||||
show_tooptips = true;
|
||||
|
||||
@@ -1533,63 +1533,63 @@ void TextureView::terminate(bool force)
|
||||
|
||||
void TextureView::arrow (glm::vec2 movement)
|
||||
{
|
||||
Source *s = Mixer::manager().currentSource();
|
||||
if (s) {
|
||||
static float accumulator = 0.f;
|
||||
accumulator += dt_;
|
||||
// Source *s = Mixer::manager().currentSource();
|
||||
// if (s) {
|
||||
// static float accumulator = 0.f;
|
||||
// accumulator += dt_;
|
||||
|
||||
glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
glm::vec3 gl_Position_to = Rendering::manager().unProject(movement, scene.root()->transform_);
|
||||
glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
// glm::vec3 gl_Position_from = Rendering::manager().unProject(glm::vec2(0.f), scene.root()->transform_);
|
||||
// glm::vec3 gl_Position_to = Rendering::manager().unProject(movement, scene.root()->transform_);
|
||||
// glm::vec3 gl_delta = gl_Position_to - gl_Position_from;
|
||||
|
||||
Group *sourceNode = s->group(mode_);
|
||||
glm::vec3 alt_move_ = sourceNode->translation_;
|
||||
if (UserInterface::manager().altModifier()) {
|
||||
if (accumulator > 100.f)
|
||||
{
|
||||
// precise movement with SHIFT
|
||||
if ( UserInterface::manager().shiftModifier() ) {
|
||||
alt_move_ += glm::sign(gl_delta) * 0.0011f;
|
||||
sourceNode->translation_.x = ROUND(alt_move_.x, 1000.f);
|
||||
sourceNode->translation_.y = ROUND(alt_move_.y, 1000.f);
|
||||
}
|
||||
else {
|
||||
alt_move_ += glm::sign(gl_delta) * 0.11f;
|
||||
sourceNode->translation_.x = ROUND(alt_move_.x, 10.f);
|
||||
sourceNode->translation_.y = ROUND(alt_move_.y, 10.f);
|
||||
}
|
||||
accumulator = 0.f;
|
||||
}
|
||||
}
|
||||
else {
|
||||
sourceNode->translation_ += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
|
||||
accumulator = 0.f;
|
||||
alt_move_ = sourceNode->translation_;
|
||||
}
|
||||
// Group *sourceNode = s->group(mode_);
|
||||
// glm::vec3 alt_move_ = sourceNode->translation_;
|
||||
// if (UserInterface::manager().altModifier()) {
|
||||
// if (accumulator > 100.f)
|
||||
// {
|
||||
// // precise movement with SHIFT
|
||||
// if ( UserInterface::manager().shiftModifier() ) {
|
||||
// alt_move_ += glm::sign(gl_delta) * 0.0011f;
|
||||
// sourceNode->translation_.x = ROUND(alt_move_.x, 1000.f);
|
||||
// sourceNode->translation_.y = ROUND(alt_move_.y, 1000.f);
|
||||
// }
|
||||
// else {
|
||||
// alt_move_ += glm::sign(gl_delta) * 0.11f;
|
||||
// sourceNode->translation_.x = ROUND(alt_move_.x, 10.f);
|
||||
// sourceNode->translation_.y = ROUND(alt_move_.y, 10.f);
|
||||
// }
|
||||
// accumulator = 0.f;
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// sourceNode->translation_ += gl_delta * ARROWS_MOVEMENT_FACTOR * dt_;
|
||||
// accumulator = 0.f;
|
||||
// alt_move_ = sourceNode->translation_;
|
||||
// }
|
||||
|
||||
// store action in history
|
||||
std::ostringstream info;
|
||||
info << "Texture Shift " << std::fixed << std::setprecision(3) << sourceNode->translation_.x;
|
||||
info << ", " << sourceNode->translation_.y ;
|
||||
current_action_ = s->name() + ": " + info.str();
|
||||
// // store action in history
|
||||
// std::ostringstream info;
|
||||
// info << "Texture Shift " << std::fixed << std::setprecision(3) << sourceNode->translation_.x;
|
||||
// info << ", " << sourceNode->translation_.y ;
|
||||
// current_action_ = s->name() + ": " + info.str();
|
||||
|
||||
// request update
|
||||
s->touch();
|
||||
}
|
||||
else if (edit_source_) {
|
||||
if (edit_source_->maskShader()->mode == MaskShader::PAINT) {
|
||||
if (mask_cursor_paint_ > 0) {
|
||||
glm::vec2 b = 0.02f * movement;
|
||||
Settings::application.brush.x = CLAMP(Settings::application.brush.x+b.x, BRUSH_MIN_SIZE, BRUSH_MAX_SIZE);
|
||||
Settings::application.brush.y = CLAMP(Settings::application.brush.y+b.y, BRUSH_MIN_PRESS, BRUSH_MAX_PRESS);
|
||||
}
|
||||
}
|
||||
else if (edit_source_->maskShader()->mode == MaskShader::SHAPE) {
|
||||
if (mask_cursor_shape_ > 0) {
|
||||
float b = -0.02f * movement.y;
|
||||
edit_source_->maskShader()->blur = CLAMP(edit_source_->maskShader()->blur+b, SHAPE_MIN_BLUR, SHAPE_MAX_BLUR);
|
||||
edit_source_->touch(Source::SourceUpdate_Mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
// // request update
|
||||
// s->touch();
|
||||
// }
|
||||
// else if (edit_source_) {
|
||||
// if (edit_source_->maskShader()->mode == MaskShader::PAINT) {
|
||||
// if (mask_cursor_paint_ > 0) {
|
||||
// glm::vec2 b = 0.02f * movement;
|
||||
// Settings::application.brush.x = CLAMP(Settings::application.brush.x+b.x, BRUSH_MIN_SIZE, BRUSH_MAX_SIZE);
|
||||
// Settings::application.brush.y = CLAMP(Settings::application.brush.y+b.y, BRUSH_MIN_PRESS, BRUSH_MAX_PRESS);
|
||||
// }
|
||||
// }
|
||||
// else if (edit_source_->maskShader()->mode == MaskShader::SHAPE) {
|
||||
// if (mask_cursor_shape_ > 0) {
|
||||
// float b = -0.02f * movement.y;
|
||||
// edit_source_->maskShader()->blur = CLAMP(edit_source_->maskShader()->blur+b, SHAPE_MIN_BLUR, SHAPE_MAX_BLUR);
|
||||
// edit_source_->touch(Source::SourceUpdate_Mask);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -310,8 +310,8 @@ void UserInterface::handleKeyboard()
|
||||
Mixer::manager().view()->selectAll();
|
||||
}
|
||||
else if (ImGui::IsKeyPressed( Control::layoutKey(GLFW_KEY_R), false )) {
|
||||
// toggle recording stop / start (or save and continue if + ALT modifier)
|
||||
outputcontrol.ToggleRecord(alt_modifier_active);
|
||||
// toggle recording stop / start (or save and continue if + SHIFT modifier)
|
||||
outputcontrol.ToggleRecord(shift_modifier_active);
|
||||
}
|
||||
else if (ImGui::IsKeyPressed( Control::layoutKey(GLFW_KEY_Z), false )) {
|
||||
if (shift_modifier_active)
|
||||
@@ -444,8 +444,15 @@ 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);
|
||||
|
||||
// get mouse coordinates and prevent invalid values
|
||||
static glm::vec2 _prev_mousepos = glm::vec2(0.f);
|
||||
glm::vec2 mousepos = _prev_mousepos;
|
||||
if (io.MousePos.x > -1 && io.MousePos.y > -1) {
|
||||
mousepos = glm::vec2 (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));
|
||||
_prev_mousepos = 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);
|
||||
@@ -503,8 +510,12 @@ void UserInterface::handleMouse()
|
||||
mousedown = true;
|
||||
|
||||
// initiate Mouse pointer from position at mouse down event
|
||||
if (alt_modifier_active || Settings::application.mouse_pointer_lock) {
|
||||
MousePointer::manager().setActiveMode( (Pointer::Mode) Settings::application.mouse_pointer );
|
||||
MousePointer::manager().active()->setStrength( Settings::application.mouse_pointer_strength[Settings::application.mouse_pointer] );
|
||||
}
|
||||
else
|
||||
MousePointer::manager().setActiveMode( Pointer::POINTER_DEFAULT );
|
||||
|
||||
// ask the view what was picked
|
||||
picked = Mixer::manager().view()->pick(mousepos);
|
||||
@@ -2893,44 +2904,77 @@ void Navigator::Render()
|
||||
RenderMousePointerSelector(iconsize);
|
||||
|
||||
// List of icons for View selection
|
||||
static uint view_options_timeout = 0;
|
||||
static ImVec2 view_options_pos = ImGui::GetCursorScreenPos();
|
||||
|
||||
bool selected_view[View::INVALID] = { };
|
||||
selected_view[ Settings::application.current_view ] = true;
|
||||
int previous_view = Settings::application.current_view;
|
||||
|
||||
if (ImGui::Selectable( ICON_FA_BULLSEYE, &selected_view[View::MIXING], 0, iconsize))
|
||||
{
|
||||
UserInterface::manager().setView(View::MIXING);
|
||||
view_pannel_visible = previous_view == Settings::application.current_view;
|
||||
if (previous_view == Settings::application.current_view) {
|
||||
ImGui::OpenPopup( "PopupViewOptions" );
|
||||
view_options_pos = ImGui::GetCursorScreenPos();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
|
||||
tooltip = {"Mixing ", "F1"};
|
||||
view_options_timeout = 0;
|
||||
}
|
||||
|
||||
if (ImGui::Selectable( ICON_FA_OBJECT_UNGROUP , &selected_view[View::GEOMETRY], 0, iconsize))
|
||||
{
|
||||
UserInterface::manager().setView(View::GEOMETRY);
|
||||
view_pannel_visible = previous_view == Settings::application.current_view;
|
||||
if (previous_view == Settings::application.current_view) {
|
||||
ImGui::OpenPopup( "PopupViewOptions" );
|
||||
view_options_pos = ImGui::GetCursorScreenPos();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
|
||||
tooltip = {"Geometry ", "F2"};
|
||||
view_options_timeout = 0;
|
||||
}
|
||||
|
||||
if (ImGui::Selectable( ICON_FA_LAYER_GROUP, &selected_view[View::LAYER], 0, iconsize))
|
||||
{
|
||||
UserInterface::manager().setView(View::LAYER);
|
||||
view_pannel_visible = previous_view == Settings::application.current_view;
|
||||
if (previous_view == Settings::application.current_view) {
|
||||
ImGui::OpenPopup( "PopupViewOptions" );
|
||||
view_options_pos = ImGui::GetCursorScreenPos();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
|
||||
tooltip = {"Layers ", "F3"};
|
||||
view_options_timeout = 0;
|
||||
}
|
||||
|
||||
if (ImGui::Selectable( ICON_FA_CHESS_BOARD, &selected_view[View::TEXTURE], 0, iconsize))
|
||||
{
|
||||
UserInterface::manager().setView(View::TEXTURE);
|
||||
view_pannel_visible = previous_view == Settings::application.current_view;
|
||||
if (previous_view == Settings::application.current_view) {
|
||||
ImGui::OpenPopup( "PopupViewOptions" );
|
||||
view_options_pos = ImGui::GetCursorScreenPos();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
|
||||
tooltip = {"Texturing ", "F4"};
|
||||
view_options_timeout = 0;
|
||||
}
|
||||
|
||||
if (ImGui::Selectable( ICON_FA_TV, &selected_view[View::DISPLAYS], 0, iconsize))
|
||||
{
|
||||
UserInterface::manager().setView(View::DISPLAYS);
|
||||
view_pannel_visible = previous_view == Settings::application.current_view;
|
||||
if (previous_view == Settings::application.current_view) {
|
||||
ImGui::OpenPopup( "PopupViewOptions" );
|
||||
view_options_pos = ImGui::GetCursorScreenPos();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
|
||||
tooltip = {"Displays ", "F5"};
|
||||
view_options_timeout = 0;
|
||||
}
|
||||
|
||||
ImVec2 pos = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPos(pos + ImVec2(0.f, style.WindowPadding.y));
|
||||
@@ -2950,6 +2994,9 @@ void Navigator::Render()
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
// render the "PopupViewOptions"
|
||||
RenderViewOptions(&view_options_timeout, view_options_pos, iconsize);
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
@@ -2964,10 +3011,6 @@ void Navigator::Render()
|
||||
else
|
||||
_timeout_tooltip = 0;
|
||||
|
||||
// Rendering of special side pannel for view zoom
|
||||
if ( view_pannel_visible && !pannel_visible_ )
|
||||
RenderViewPannel( ImVec2(width_, sourcelist_height), ImVec2(width_*0.8f, height_ - sourcelist_height) );
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopFont();
|
||||
|
||||
@@ -3019,38 +3062,38 @@ void Navigator::Render()
|
||||
|
||||
}
|
||||
|
||||
void Navigator::RenderViewPannel(ImVec2 draw_pos , ImVec2 draw_size)
|
||||
void Navigator::RenderViewOptions(uint *timeout, const ImVec2 &pos, const ImVec2 &size)
|
||||
{
|
||||
ImGui::SetNextWindowPos( draw_pos, ImGuiCond_Always );
|
||||
ImGui::SetNextWindowSize( draw_size, ImGuiCond_Always );
|
||||
ImGui::SetNextWindowBgAlpha(0.95f); // Transparent background
|
||||
if (ImGui::Begin("##ViewPannel", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNav))
|
||||
ImGuiContext& g = *GImGui;
|
||||
|
||||
ImGui::SetNextWindowPos( pos + ImVec2(size.x + g.Style.WindowPadding.x, -size.y), ImGuiCond_Always );
|
||||
ImGui::SetNextWindowSize( ImVec2(size.x * 7.f, size.y), ImGuiCond_Always );
|
||||
if (ImGui::BeginPopup( "PopupViewOptions" ))
|
||||
{
|
||||
ImGui::SetCursorPosX(10.f);
|
||||
ImGui::SetCursorPosY(10.f);
|
||||
if (ImGuiToolkit::IconButton(8,7)) {
|
||||
// vertical padding
|
||||
ImGui::SetCursorPosY( ImGui::GetCursorPosY() + g.Style.WindowPadding.y * 0.5f );
|
||||
|
||||
// reset zoom
|
||||
if (ImGuiToolkit::IconButton(8,7)) {
|
||||
Mixer::manager().view((View::Mode)Settings::application.current_view)->recenter();
|
||||
}
|
||||
|
||||
draw_size.x *= 0.5;
|
||||
ImGui::SetCursorPosX( 10.f);
|
||||
draw_size.y -= ImGui::GetCursorPosY() + 10.f;
|
||||
// percent zoom slider
|
||||
int percent_zoom = Mixer::manager().view((View::Mode)Settings::application.current_view)->size();
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.1, 0.1, 0.1, 0.95));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.14, 0.14, 0.14, 0.95));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.14, 0.14, 0.14, 0.95));
|
||||
ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.9, 0.9, 0.9, 0.95));
|
||||
if (ImGui::VSliderInt("##z", draw_size, &percent_zoom, 0, 100, "") )
|
||||
{
|
||||
ImGui::SameLine(0, IMGUI_SAME_LINE);
|
||||
ImGui::SetNextItemWidth(-1.f);
|
||||
if (ImGui::SliderInt("##zoom", &percent_zoom, 0, 100, "%d %%" )) {
|
||||
Mixer::manager().view((View::Mode)Settings::application.current_view)->resize(percent_zoom);
|
||||
}
|
||||
ImGui::PopStyleColor(4);
|
||||
if (ImGui::IsItemActive() || ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Zoom %d %%", percent_zoom);
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// timer to close popup like a tooltip
|
||||
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
|
||||
*timeout=0;
|
||||
else if ( (*timeout)++ > 10)
|
||||
ImGui::CloseCurrentPopup();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
// Source pannel : *s was checked before
|
||||
@@ -4626,14 +4669,18 @@ void Navigator::RenderMousePointerSelector(const ImVec2 &size)
|
||||
/// 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) ) {
|
||||
if ( ImGui::InvisibleButton("##MenuMousePointerButton", size) /*|| ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)*/ ) {
|
||||
|
||||
counter_menu_timeout=0;
|
||||
if (enabled)
|
||||
ImGui::OpenPopup( "MenuMousePointer" );
|
||||
}
|
||||
ImVec2 bottom = ImGui::GetCursorScreenPos();
|
||||
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
|
||||
ImGuiToolkit::ToolTip("Alternative cursor", "ALT");
|
||||
counter_menu_timeout=0;
|
||||
}
|
||||
|
||||
// Change color of icons depending on context menu status
|
||||
const ImVec4* colors = ImGui::GetStyle().Colors;
|
||||
if (enabled)
|
||||
@@ -4642,10 +4689,10 @@ void Navigator::RenderMousePointerSelector(const ImVec2 &size)
|
||||
ImGui::PushStyleColor( ImGuiCol_Text, colors[ImGuiCol_TextDisabled] );
|
||||
|
||||
// Draw centered icon of Mouse pointer
|
||||
ImVec2 margin = (size - ImVec2(g.FontSize, g.FontSize)) * 0.42f;
|
||||
ImVec2 margin = (size - ImVec2(g.FontSize, g.FontSize)) * 0.5f;
|
||||
ImGui::SetCursorPos( top + margin );
|
||||
|
||||
if ( Settings::application.mouse_pointer > 0 ) {
|
||||
if ( UserInterface::manager().altModifier() || Settings::application.mouse_pointer_lock) {
|
||||
// icon with corner erased
|
||||
ImGuiToolkit::Icon(ICON_POINTER_OPTION);
|
||||
|
||||
@@ -4672,7 +4719,7 @@ void Navigator::RenderMousePointerSelector(const ImVec2 &size)
|
||||
if (ImGui::BeginPopup( "MenuMousePointer" ))
|
||||
{
|
||||
// loop over all mouse pointer modes
|
||||
for ( size_t m = Pointer::POINTER_DEFAULT; m < Pointer::POINTER_INVALID; ++m) {
|
||||
for ( size_t m = Pointer::POINTER_GRID; m < Pointer::POINTER_INVALID; ++m) {
|
||||
bool on = m == (size_t) Settings::application.mouse_pointer;
|
||||
std::tuple<int, int, std::string> mode = Pointer::Modes.at(m);
|
||||
// show icon of mouse mode and set mouse pointer if selected
|
||||
@@ -4682,6 +4729,16 @@ void Navigator::RenderMousePointerSelector(const ImVec2 &size)
|
||||
ImGui::SameLine(0, IMGUI_SAME_LINE);
|
||||
}
|
||||
|
||||
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
|
||||
ImGui::SetCursorPosY(margin.y);
|
||||
ImGui::TextDisabled(" |");
|
||||
ImGui::SameLine(0, IMGUI_SAME_LINE);
|
||||
ImGuiToolkit::ButtonToggle(Settings::application.mouse_pointer_lock ? ICON_FA_LOCK " ALT LOCK" : ICON_FA_UNLOCK " ALT LOCK",
|
||||
&Settings::application.mouse_pointer_lock,
|
||||
"Activate the selected alternative mouse pointer by pressing the ALT key.\n\n"
|
||||
ICON_FA_LOCK " ALT LOCK keeps the alternative mouse pointer active.");
|
||||
ImGui::PopFont();
|
||||
|
||||
// timer to close menu like a tooltip
|
||||
if (ImGui::IsWindowHovered())
|
||||
counter_menu_timeout=0;
|
||||
|
||||
@@ -69,7 +69,7 @@ class Navigator
|
||||
void RenderMainPannelSettings();
|
||||
void RenderTransitionPannel();
|
||||
void RenderNewPannel();
|
||||
void RenderViewPannel(ImVec2 draw_pos, ImVec2 draw_size);
|
||||
void RenderViewOptions(uint *timeout, const ImVec2 &pos, const ImVec2 &size);
|
||||
void RenderMousePointerSelector(const ImVec2 &size);
|
||||
|
||||
public:
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
#define MENU_RECORD ICON_FA_CIRCLE " Record"
|
||||
#define SHORTCUT_RECORD CTRL_MOD "R"
|
||||
#define MENU_RECORDCONT ICON_FA_STOP_CIRCLE " Save & continue"
|
||||
#define SHORTCUT_RECORDCONT CTRL_MOD "Alt+R"
|
||||
#define SHORTCUT_RECORDCONT CTRL_MOD "Shift+R"
|
||||
#define MENU_CAPTUREFRAME ICON_FA_CAMERA_RETRO " Capture frame"
|
||||
#define SHORTCUT_CAPTURE_DISPLAY "F11"
|
||||
#define SHORTCUT_CAPTURE_PLAYER "F10"
|
||||
|
||||
Reference in New Issue
Block a user