Draft CANVAS editing

Starting to add Canvas edit in Geometry view
This commit is contained in:
Bruno Herbelin
2025-01-25 12:05:51 +01:00
parent 928e798d86
commit ea4615ea65
6 changed files with 517 additions and 184 deletions

View File

@@ -583,6 +583,15 @@ void DisplaysView::draw()
// Add / Remove windows
ImGui::SameLine();
if ( Settings::application.num_output_windows > 0 ) {
if (ImGuiToolkit::IconButton(19, 4, "Less windows")) {
--Settings::application.num_output_windows;
current_window_ = -1;
}
}
else
ImGuiToolkit::Icon(19, 4, false);
ImGui::SameLine();
if ( Settings::application.num_output_windows < MAX_OUTPUT_WINDOW) {
if (ImGuiToolkit::IconButton(18, 4, "More windows")) {
++Settings::application.num_output_windows;
@@ -592,15 +601,6 @@ void DisplaysView::draw()
}
else
ImGuiToolkit::Icon(18, 4, false);
ImGui::SameLine();
if ( Settings::application.num_output_windows > 0 ) {
if (ImGuiToolkit::IconButton(19, 4, "Less windows")) {
--Settings::application.num_output_windows;
current_window_ = -1;
}
}
else
ImGuiToolkit::Icon(19, 4, false);
// Modify current window
if (current_window_ > -1) {

View File

@@ -47,6 +47,16 @@
#include "GeometryView.h"
const char* GeometryView::editor_icons[2] = { ICON_FA_OBJECT_UNGROUP, ICON_FA_BORDER_ALL };
const char *GeometryView::editor_names[2] = {"Edit sources", " Edit canvas"};
void Canvas::setCurrent(bool on)
{
root_->visible_ = true;
frames_->setActive( on ? 1 : 0);
}
GeometryView::GeometryView() : View(GEOMETRY)
{
scene.root()->scale_ = glm::vec3(GEOMETRY_DEFAULT_SCALE, GEOMETRY_DEFAULT_SCALE, 1.0f);
@@ -62,9 +72,52 @@ GeometryView::GeometryView() : View(GEOMETRY)
output_surface_ = new Surface;
output_surface_->visible_ = false;
scene.fg()->attach(output_surface_);
Frame *border = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
border->color = glm::vec4( COLOR_FRAME, 1.f );
scene.fg()->attach(border);
// Geometry surfaces
canvas_ = std::vector<Canvas>(MAX_OUTPUT_CANVAS);
for (auto &c : canvas_) {
// root node
c.root_ = new Group;
scene.fg()->attach(c.root_);
c.root_->visible_ = false;
// attach all modes of frame to the switch node
c.frames_ = new Switch;
c.root_->attach(c.frames_);
// frames_[0] : not current
Frame *frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
frame->color = glm::vec4(COLOR_FRAME, 0.95f);
c.frames_->attach(frame);
// frames_[1] : current
c.overlays_ = new Group;
c.frames_->attach(c.overlays_);
frame = new Frame(Frame::SHARP, Frame::LARGE, Frame::GLOW); // current
frame->color = glm::vec4(COLOR_FRAME, 1.f);
c.overlays_->attach(frame);
c.menu_ = new Handles(Handles::MENU);
c.menu_->color = glm::vec4(COLOR_FRAME, 1.f);
c.overlays_->attach(c.menu_);
c.handles_[Handles::RESIZE] = new Handles(Handles::RESIZE);
c.handles_[Handles::RESIZE]->color = glm::vec4(COLOR_FRAME, 1.f);
c.overlays_->attach(c.handles_[Handles::RESIZE]);
c.handles_[Handles::RESIZE_H] = new Handles(Handles::RESIZE_H);
c.handles_[Handles::RESIZE_H]->color = glm::vec4(COLOR_FRAME, 1.f);
c.overlays_->attach(c.handles_[Handles::RESIZE_H]);
c.handles_[Handles::RESIZE_V] = new Handles(Handles::RESIZE_V);
c.handles_[Handles::RESIZE_V]->color = glm::vec4(COLOR_FRAME, 1.f);
c.overlays_->attach(c.handles_[Handles::RESIZE_V]);
}
// first surface is always visible
canvas_current_ = 0;
canvas_[canvas_current_].setCurrent(false);
canvas_stored_status_ = new Group;
// User interface foreground
//
@@ -131,7 +184,7 @@ GeometryView::GeometryView() : View(GEOMETRY)
scene.fg()->attach(overlay_scaling_);
overlay_scaling_->visible_ = false;
border = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
Frame *border = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
border->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 0.2f );
overlay_crop_ = border;
scene.fg()->attach(overlay_crop_);
@@ -235,6 +288,9 @@ void GeometryView::draw()
std::vector<Node *> overlays;
uint workspaces_counts_[Source::WORKSPACE_ANY+1] = {0};
uint hidden_count_ = 0;
// fills list of sources only in SOURCE EDIT MODE
if (editor_mode_ == EDIT_SOURCES) {
for (auto source_iter = Mixer::manager().session()->begin();
source_iter != Mixer::manager().session()->end(); ++source_iter) {
// count if it is visible
@@ -254,6 +310,7 @@ void GeometryView::draw()
}
hidden_count_ += (*source_iter)->visible() ? 0 : 1;
}
}
// 0. prepare projection for draw visitors
glm::mat4 projection = Rendering::manager().Projection();
@@ -266,6 +323,7 @@ void GeometryView::draw()
DrawVisitor draw_rendering(output_surface_, projection, true);
scene.accept(draw_rendering);
if (!overlays.empty()) {
// 3. Draw frames and icons of sources in the current workspace
DrawVisitor draw_overlays(overlays, projection);
scene.accept(draw_overlays);
@@ -281,6 +339,7 @@ void GeometryView::draw()
// Always restore current source after draw
s->setMode(Source::CURRENT);
}
}
// 5. Finally, draw overlays of view
DrawVisitor draw_foreground(scene.fg(), projection);
@@ -311,10 +370,69 @@ void GeometryView::draw()
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.15f, 0.15f, 0.15f, 0.99f));
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f));
#ifdef ENABLE_CANVAS
// SELECT EDITOR MODE
ImGui::SetNextItemWidth(ImGui::GetTextLineHeightWithSpacing() * 2.6);
if (ImGui::Button(
std::string(std::string(editor_icons[editor_mode_]) + " " + ICON_FA_SORT_DOWN)
.c_str()))
ImGui::OpenPopup("Geometry_mode_menu_popup");
if (ImGui::IsItemHovered())
ImGuiToolkit::ToolTip(editor_names[editor_mode_]);
if (ImGui::BeginPopup("Geometry_mode_menu_popup")) {
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
for (int m = GeometryView::EDIT_SOURCES; m <= GeometryView::EDIT_CANVAS; ++m) {
if (ImGui::Selectable(
std::string(std::string(editor_icons[m]) + " " + editor_names[m]).c_str())) {
// change edit mode
editor_mode_ = m;
// cancel selection of sources
if (m != GeometryView::EDIT_SOURCES){
Mixer::selection().clear();
canvas_[canvas_current_].setCurrent(true);
}
else {
canvas_[canvas_current_].setCurrent(false);
}
}
}
ImGui::PopFont();
ImGui::EndPopup();
}
#endif
// SURFACES EDIT OPTIONS
if (editor_mode_ == EDIT_CANVAS) {
// add / remove surfaces
ImGui::SameLine(0, IMGUI_SAME_LINE);
if (Settings::application.num_output_surfaces > 1) {
if (ImGuiToolkit::IconButton(19, 4, "Less canvas")) {
Settings::application.num_output_surfaces--;
}
}
else
ImGuiToolkit::Icon(19, 4, false);
ImGui::SameLine(0, IMGUI_SAME_LINE);
if (Settings::application.num_output_surfaces < MAX_OUTPUT_CANVAS) {
if (ImGuiToolkit::IconButton(18, 4, "More canvas")) {
Settings::application.num_output_surfaces++;
}
} else
ImGuiToolkit::Icon(18, 4, false);
}
// SOURCES EDIT OPTIONS
else {
// toggle sources visibility flag
std::string _label = Settings::application.views[mode_].ignore_mix ? "Show " : "Hide ";
_label += "non visible sources\n(";
_label += std::to_string(hidden_count_) + " source" + (hidden_count_>1?"s are ":" is ") + "outside mixing circle)";
ImGui::SameLine(0, IMGUI_SAME_LINE);
ImGuiToolkit::ButtonIconToggle(12, 0, &Settings::application.views[mode_].ignore_mix, _label.c_str());
// select layers visibility
@@ -337,6 +455,8 @@ void GeometryView::draw()
Settings::application.current_workspace = (Settings::application.current_workspace+1)%4;
}
}
ImGui::PopStyleColor(6);
ImGui::End();
}
@@ -565,6 +685,10 @@ std::pair<Node *, glm::vec2> GeometryView::pick(glm::vec2 P)
// picking visitor found nodes?
if ( !pv.empty() ) {
// SOURCE EDIT
if (editor_mode_ == EDIT_SOURCES) {
// keep current source active if it is clicked
Source *current = Mixer::manager().currentSource();
if (current != nullptr) {
@@ -612,7 +736,7 @@ std::pair<Node *, glm::vec2> GeometryView::pick(glm::vec2 P)
else if ( UserInterface::manager().ctrlModifier() && pick.first == current->lock_ ) {
lock(current, false);
pick = { current->locker_, pick.second };
// pick = { nullptr, glm::vec2(0.f) };
// pick = { nullptr, glm::vec2(0.f) };
}
// pick on the open lock icon; lock source and cancel pick
else if ( UserInterface::manager().ctrlModifier() && pick.first == current->unlock_ ) {
@@ -693,11 +817,31 @@ std::pair<Node *, glm::vec2> GeometryView::pick(glm::vec2 P)
}
}
}
// CANVAS EDIT
else {
// picking node
pick = pv.back();
if (pick.first == canvas_[canvas_current_].menu_) {
// TODO context menu canvas
g_printerr("Canvas MENU\n");
}
// else {
// g_printerr("Pick x %f y %f\n", pick.second.x, pick.second.y);
// }
}
}
return pick;
}
bool GeometryView::canSelect(Source *s) {
bool GeometryView::canSelect(Source *s)
{
if (editor_mode_ != EDIT_SOURCES)
return false;
return ( s!=nullptr && View::canSelect(s) && s->ready() &&
(Settings::application.views[mode_].ignore_mix || s->visible()) &&
@@ -730,6 +874,71 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p
glm::vec3 scene_from = Rendering::manager().unProject(from, scene.root()->transform_);
glm::vec3 scene_to = Rendering::manager().unProject(to, scene.root()->transform_);
if (editor_mode_ == EDIT_CANVAS && pick.first) {
const glm::mat4 scene_to_canvas_transform = glm::inverse(canvas_stored_status_->transform_);
// const glm::mat4 canvas_to_scene_transform = scene_to_canvas_transform;
// which corner was picked ?
glm::vec2 corner = glm::round(pick.second);
// keep transform from source center to opposite corner
const glm::mat4 canvas_to_corner_transform = glm::translate(glm::identity<glm::mat4>(),
glm::vec3(corner, 0.f));
// transformation from scene to corner:
const glm::mat4 scene_to_corner_transform = canvas_to_corner_transform
* scene_to_canvas_transform;
const glm::mat4 corner_to_scene_transform = glm::inverse(scene_to_corner_transform);
// clamp coordinates of target cursor position to remain inside output surface
scene_to = glm::clamp(scene_to, glm::vec3(-output_surface_->scale_.x, -1.f, -10.f),
glm::vec3(output_surface_->scale_.x, 1.f, 10.f));
if (pick.first == canvas_[canvas_current_].handles_[Handles::RESIZE]) {
// hide other grips
canvas_[canvas_current_].menu_->visible_ = false;
canvas_[canvas_current_].handles_[Handles::RESIZE_H]->visible_ = false;
canvas_[canvas_current_].handles_[Handles::RESIZE_V]->visible_ = false;
// inform on which corner should be overlayed (opposite)
canvas_[canvas_current_].handles_[Handles::RESIZE]->overlayActiveCorner(-corner);
//
// Manipulate the handle in the SCENE coordinates to apply grid snap
//
glm::vec4 handle = corner_to_scene_transform * glm::vec4(corner * 2.f, 0.f, 1.f);
// move the corner we hold by the mouse translation (in scene reference frame)
handle = glm::translate(glm::identity<glm::mat4>(), scene_to - scene_from) * handle;
// snap handle coordinates to grid (if active)
if (grid->active())
handle = grid->snap(handle);
// Compute handleNODE_UPPER_RIGHT coordinates back in CORNER reference frame
handle = scene_to_corner_transform * handle;
// The scaling factor is computed by dividing new handle coordinates with the ones before transform
glm::vec2 corner_scaling = glm::vec2(handle) / glm::vec2(corner * 2.f);
// proportional SCALING with SHIFT
if (UserInterface::manager().shiftModifier()) {
corner_scaling = glm::vec2(glm::compMax(corner_scaling));
}
// Apply scaling to the source
canvas_[canvas_current_].root_->scale_ = canvas_stored_status_->scale_ * glm::vec3(corner_scaling, 1.f);
//
// Adjust translation
//
// The center of the source in CORNER reference frame
glm::vec4 corner_center = glm::vec4( corner, 0.f, 1.f);
// scale center of source in CORNER reference frame
corner_center = glm::scale(glm::identity<glm::mat4>(), glm::vec3(corner_scaling, 1.f)) * corner_center;
// convert center back into scene reference frame
corner_center = corner_to_scene_transform * corner_center;
// Apply scaling to the source
canvas_[canvas_current_].root_->translation_ = glm::vec3(corner_center);
}
// update cursor
return ret;
}
// No source is given
if (!s) {
@@ -886,6 +1095,7 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p
const glm::mat4 scene_to_corner_transform = source_to_corner_transform * scene_to_source_transform;
const glm::mat4 corner_to_scene_transform = glm::inverse(scene_to_corner_transform);
// picking on a Node
if (pick.first == s->handles_[mode_][Handles::NODE_LOWER_LEFT]) {
// hide other grips
@@ -1198,7 +1408,6 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p
s->handles_[mode_][Handles::MENU]->visible_ = false;
s->handles_[mode_][Handles::EDIT_CROP]->visible_ = false;
// get stored status
// glm::vec3 node_pos = glm::vec3( -s->stored_status_->data_[0].z, 0.f, 0.f);
glm::vec3 node_pos = glm::vec3( -s->stored_status_->data_[0].w, 0.f, 0.f);
// Compute target coordinates of manipulated handle into SCENE reference frame
node_pos = source_to_scene_transform * glm::vec4(node_pos, 1.f);
@@ -1208,7 +1417,6 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p
node_pos = scene_to_source_transform * glm::vec4(node_pos, 1.f);
// apply to source Node and to handles
// sourceNode->data_[0].z = - CLAMP( node_pos.x, -1.f, 0.f );
sourceNode->data_[0].w = - CLAMP( node_pos.x, -1.f, 0.f );
info << "Corner round " << std::fixed << std::setprecision(3) << sourceNode->data_[0].w;
}
@@ -1558,6 +1766,21 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p
return ret;
}
void GeometryView::initiate()
{
if (editor_mode_ == EDIT_SOURCES)
View::initiate();
else if (!current_action_ongoing_ ) {
// store status current canvas
canvas_stored_status_->copyTransform(canvas_[canvas_current_].root_);
canvas_stored_status_->update(0.f);
// initiated
current_action_ = "";
current_action_ongoing_ = true;
}
}
void GeometryView::terminate(bool force)
{
View::terminate(force);
@@ -1594,6 +1817,16 @@ void GeometryView::terminate(bool force)
overlay_selection_active_ = false;
// restore all handles canvas
for (auto &c : canvas_) {
c.menu_->visible_ = true;
c.handles_[Handles::RESIZE]->visible_ = true;
c.handles_[Handles::RESIZE]->overlayActiveCorner( glm::vec2(0.f, 0.f) );
c.handles_[Handles::RESIZE_H]->visible_ = true;
c.handles_[Handles::RESIZE_V]->visible_ = true;
}
// reset grid
adaptGridToSource();
}

View File

@@ -1,8 +1,33 @@
#ifndef GEOMETRYVIEW_H
#define GEOMETRYVIEW_H
// #define ENABLE_CANVAS
#include "View.h"
struct Canvas
{
bool current_;
Group *root_;
Switch *frames_;
Group *overlays_;
Handles *handles_[3];
Handles *menu_;
Canvas()
{
current_ = false;
root_ = nullptr;
frames_ = nullptr;
overlays_ = nullptr;
menu_ = nullptr;
handles_[0] = nullptr;
handles_[1] = nullptr;
handles_[2] = nullptr;
}
void setCurrent(bool on);
};
class GeometryView : public View
{
@@ -20,6 +45,7 @@ public:
std::pair<Node *, glm::vec2> pick(glm::vec2 P) override;
Cursor grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair<Node *, glm::vec2> pick) override;
void initiate () override;
void terminate(bool force = false) override;
void arrow (glm::vec2) override;
Cursor over (glm::vec2) override;
@@ -38,6 +64,18 @@ private:
Node *overlay_scaling_grid_;
Node *overlay_crop_;
std::vector<Canvas> canvas_;
uint canvas_current_;
Group *canvas_stored_status_;
enum GeometryModes {
EDIT_SOURCES = 0,
EDIT_CANVAS = 1
};
uint editor_mode_;
static const char* editor_icons[2];
static const char* editor_names[2];
void updateSelectionOverlay(glm::vec4 color) override;
bool overlay_selection_active_;
Group *overlay_selection_stored_status_;

View File

@@ -124,6 +124,27 @@ void Settings::Save(uint64_t runtime, const std::string &filename)
pRoot->InsertEndChild(windowsNode);
}
// Surfaces
{
XMLElement *surfacesNode = xmlDoc.NewElement( "OutputSurfaces" );
surfacesNode->SetAttribute("num_output_surfaces", application.num_output_surfaces);
for (int i = 0; i < (int) application.surfaces.size(); ++i)
{
const Settings::CanvasConfig& w = application.surfaces[i];
XMLElement *surface = xmlDoc.NewElement( "Surface" );
surface->SetAttribute("id", i);
surface->SetAttribute("x", w.x);
surface->SetAttribute("y", w.y);
surface->SetAttribute("w", w.w);
surface->SetAttribute("h", w.h);
surfacesNode->InsertEndChild(surface);
}
pRoot->InsertEndChild(surfacesNode);
}
// General application preferences
XMLElement *applicationNode = xmlDoc.NewElement( "Application" );
applicationNode->SetAttribute("scale", application.scale);
@@ -639,6 +660,28 @@ void Settings::Load(const std::string &filename)
}
}
// Surfaces
{
XMLElement *pElement = pRoot->FirstChildElement("OutputSurfaces");
if (pElement) {
pElement->QueryIntAttribute("num_output_surfaces", &application.num_output_surfaces);
XMLElement *surfaceNode = pElement->FirstChildElement("Surface");
for (; surfaceNode; surfaceNode = surfaceNode->NextSiblingElement()) {
Settings::CanvasConfig w;
surfaceNode->QueryIntAttribute("x", &w.x);
surfaceNode->QueryIntAttribute("y", &w.y);
surfaceNode->QueryIntAttribute("w", &w.w);
surfaceNode->QueryIntAttribute("h", &w.h);
int i = 0;
surfaceNode->QueryIntAttribute("id", &i);
application.surfaces[i] = w;
}
}
}
// Brush
XMLElement * brushnode = pRoot->FirstChildElement("Brush");
if (brushnode != nullptr) {

View File

@@ -83,6 +83,17 @@ struct WindowConfig
};
struct CanvasConfig
{
int x, y, w, h;
CanvasConfig() : x(0), y(0), w(1), h(1)
{
}
};
struct ViewConfig
{
std::string name;
@@ -332,6 +343,10 @@ struct Application
int num_output_windows;
std::vector<WindowConfig> windows;
// multiple surfaces handling
int num_output_surfaces;
std::vector<CanvasConfig> surfaces;
// recent files histories
History recentSessions;
History recentPlaylists;
@@ -380,6 +395,8 @@ struct Application
windows = std::vector<WindowConfig>(1+MAX_OUTPUT_WINDOW);
windows[0].w = 1600;
windows[0].h = 930;
num_output_surfaces = 1;
surfaces = std::vector<CanvasConfig>(MAX_OUTPUT_CANVAS);
accept_audio = false;
dialogPosition = glm::ivec2(-1, -1);
image_sequence.framerate_mode = 15;

View File

@@ -9,6 +9,7 @@
#define MAX_RECENT_HISTORY 20
#define MAX_SESSION_LEVEL 3
#define MAX_OUTPUT_WINDOW 3
#define MAX_OUTPUT_CANVAS 8
#define VIMIX_GL_VERSION "opengl3"
#define VIMIX_GLSL_VERSION "#version 150"
@@ -76,7 +77,7 @@
#define MIXING_MIN_THRESHOLD 1.3f
#define MIXING_MAX_THRESHOLD 1.9f
#define MIXING_ICON_SCALE 0.15f, 0.15f, 1.f
#define GEOMETRY_DEFAULT_SCALE 1.4f
#define GEOMETRY_DEFAULT_SCALE 1.8f
#define GEOMETRY_MIN_SCALE 0.4f
#define GEOMETRY_MAX_SCALE 7.0f
#define LAYER_DEFAULT_SCALE 0.6f
@@ -145,6 +146,7 @@
#define COLOR_MONITOR 0.90f, 0.90f, 0.90f
#define COLOR_WINDOW 0.2f, 0.85f, 0.85f
#define COLOR_MENU_HOVERED 0.3f, 0.3f, 0.3f
#define COLOR_AUTOMATON 0.95f, 0.3f, 0.3f
#define OSC_PORT_RECV_DEFAULT 7000
#define OSC_PORT_SEND_DEFAULT 7001