summaryrefslogtreecommitdiffstats
path: root/src/livecode
diff options
context:
space:
mode:
Diffstat (limited to 'src/livecode')
-rw-r--r--src/livecode/CMakeLists.txt12
-rw-r--r--src/livecode/api.cpp289
-rw-r--r--src/livecode/api.h101
-rw-r--r--src/livecode/input.cpp103
-rw-r--r--src/livecode/input.h74
5 files changed, 579 insertions, 0 deletions
diff --git a/src/livecode/CMakeLists.txt b/src/livecode/CMakeLists.txt
new file mode 100644
index 000000000..ba266998e
--- /dev/null
+++ b/src/livecode/CMakeLists.txt
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(livecode_SRC
+ api.cpp
+ input.cpp
+
+ # HEADERS
+ api.h
+ input.h
+ )
+
+add_inkscape_source("${livecode_SRC}")
diff --git a/src/livecode/api.cpp b/src/livecode/api.cpp
new file mode 100644
index 000000000..4617968c2
--- /dev/null
+++ b/src/livecode/api.cpp
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * API for the livecoding script language
+ *
+ * Authors:
+ * Sol Bekic <s+inkscape@s-ol.nu>
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "svg/stringstream.h"
+#include "svg/svg-color.h"
+#include "svg/svg.h"
+#include "document.h"
+#include "display/sp-canvas-item.h"
+
+#include "livecode/api.h"
+
+namespace Inkscape {
+namespace Livecode {
+
+API::API(SPDesktop *desktop)
+ : desktop(desktop)
+ , doc_root(nullptr)
+ , ui_root(nullptr)
+ , _mouse(*this)
+{
+}
+
+API::~API() {
+ if (doc_root) {
+ doc_root->deleteObject(true, true);
+ doc_root = nullptr;
+ }
+
+ if (ui_root) {
+ ui_root->deleteObject(true, true);
+ ui_root = nullptr;
+ }
+}
+
+Inkscape::XML::Node *API::make_rect(Geom::Rect const &rect, SPCSSAttr *css) {
+ SPDocument *doc = desktop->getDocument();
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:rect");
+
+ sp_repr_set_svg_double(repr, "x", rect.left());
+ sp_repr_set_svg_double(repr, "y", rect.top());
+ sp_repr_set_svg_double(repr, "width", rect.width());
+ sp_repr_set_svg_double(repr, "height", rect.height());
+
+ if (css) {
+ Glib::ustring css_str;
+ sp_repr_css_write_string(css, css_str);
+ sp_repr_css_attr_unref(css);
+ repr->setAttribute("style", css_str.c_str());
+ }
+
+ return repr;
+}
+
+Inkscape::XML::Node *API::make_line(Geom::Point const &p1, Geom::Point const &p2, SPCSSAttr *css) {
+ SPDocument *doc = desktop->getDocument();
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:line");
+
+ sp_repr_set_svg_double(repr, "x1", p1.x());
+ sp_repr_set_svg_double(repr, "y1", p1.y());
+ sp_repr_set_svg_double(repr, "x2", p2.x());
+ sp_repr_set_svg_double(repr, "y2", p2.y());
+
+ if (css) {
+ Glib::ustring css_str;
+ sp_repr_css_write_string(css, css_str);
+ sp_repr_css_attr_unref(css);
+ repr->setAttribute("style", css_str.c_str());
+ }
+
+ return repr;
+}
+
+Inkscape::XML::Node *API::make_path(Glib::ustring d, Geom::Point const &pos, SPCSSAttr *css) {
+ SPDocument *doc = desktop->getDocument();
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
+
+ sp_repr_set_svg_double(repr, "x", pos.x());
+ sp_repr_set_svg_double(repr, "y", pos.y());
+ repr->setAttribute("d", d.c_str());
+
+ if (css) {
+ Glib::ustring css_str;
+ sp_repr_css_write_string(css, css_str);
+ sp_repr_css_attr_unref(css);
+ repr->setAttribute("style", css_str.c_str());
+ }
+
+ return repr;
+}
+
+Inkscape::XML::Node *API::make_arrow(Geom::Point const &from, Geom::Point const &to, SPCSSAttr *css) {
+ SPDocument *doc = desktop->getDocument();
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
+
+ Geom::Point back = from - to;
+ back.normalize();
+ Geom::Point const cross_a = back.cw() + back * 2.0;
+ Geom::Point const cross_b = back.cw() + back * 2.0;
+
+ gchar* d = g_strdup_printf("M %f,%f %f,%f l %f,%f M %f,%f l %f,%f",
+ from.x(), from.y(),
+ to.x(), to.y(),
+ cross_a.x(), cross_a.y(),
+ to.x(), to.y(),
+ cross_b.x(), cross_b.y());
+ repr->setAttribute("d", d);
+ g_free(d);
+
+ Glib::ustring css_str;
+ if (css) {
+ sp_repr_css_write_string(css, css_str);
+ sp_repr_css_attr_unref(css);
+ } else {
+ css_str = "stroke: #000000;";
+ }
+ repr->setAttribute("style", css_str.c_str());
+
+ return repr;
+}
+
+namespace {
+SPCSSAttr *handle_style() {
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_set_property(css, "fill", "#ffffff");
+ sp_repr_css_set_property(css, "stroke-width", "0.2");
+ sp_repr_css_set_property(css, "stroke", "#000000");
+
+ return css;
+}
+}
+
+bool API::input_point(Glib::ustring const &id, Geom::Point *point) {
+ auto const half_handle_size = Geom::Point(2, 2);
+ auto ui_point = *point * desktop->w2d().inverse();
+ Geom::Rect const rect(ui_point - half_handle_size, ui_point + half_handle_size);
+
+ SPCSSAttr *css = handle_style();
+ guint32 color = 0xffaaaaff;
+
+ if (rect.interiorContains(_mouse.ui_pos())) {
+ hot = id;
+ sp_repr_css_set_property(css, "fill", "#ff0000");
+ }
+
+ if (hot == id && _mouse.pressed()) {
+ active = id;
+ }
+
+ bool change = false;
+
+ if (active == id) {
+ sp_repr_css_set_property(css, "fill", "#0000ff");
+
+ auto const delta = _mouse.delta();
+ change = !delta.isZero();
+ *point += delta;
+ if (_mouse.released()) {
+ active = "";
+ }
+ }
+
+ Inkscape::XML::Node *repr = make_rect(rect, css);
+ draw_ui(id, repr);
+ return change;
+}
+
+bool API::input_line(Glib::ustring const &id, Geom::Point *p1, Geom::Point *p2) {
+ bool change = false;
+ if (input_point(id + "_p1", p1)) {
+ change = true;
+ }
+ if (input_point(id + "_p2", p2)) {
+ change = true;
+ }
+ draw_doc("", make_line(*p1, *p2));
+ return change;
+}
+
+bool API::input_arrow(Glib::ustring const &id, Geom::Point *from, Geom::Point *to) {
+ bool change = false;
+ if (input_point(id + "_from", from)) {
+ change = true;
+ }
+ if (input_point(id + "_to", to)) {
+ change = true;
+ }
+ draw_doc("", make_line(*from, *to));
+ return change;
+}
+
+bool API::input_rect(Glib::ustring const &id, Geom::Rect *rect) {
+ Geom::Point min = rect->min();
+ Geom::Point max = rect->max();
+
+ bool change = false;
+ if (input_point(id + "_min", &min)) {
+ Geom::Point const delta = min - rect->min();
+ *rect += delta;
+ change = true;
+ }
+ if (input_point(id + "_max", &max)) {
+ rect->setMax(max);
+ change = true;
+ }
+
+ draw_doc("", make_rect(*rect));
+ return change;
+}
+
+Mouse const &API::mouse() {
+ return _mouse;
+}
+
+void API::push_event(GdkEvent *event) {
+ _mouse.push_event(event);
+}
+
+void API::setup_frame() {
+ hot = "";
+
+ if (doc_root) {
+ doc_root->deleteObject(true, true);
+ doc_root = nullptr;
+ }
+
+ if (ui_root) {
+ ui_root->deleteObject(true, true);
+ ui_root = nullptr;
+ }
+
+ Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
+ Inkscape::XML::Node *rdoc = xml_doc->createElement("svg:g");
+ Inkscape::XML::Node *rui = xml_doc->createElement("svg:g");
+
+ doc_root = SP_ITEM(desktop->currentLayer()->appendChildRepr(rdoc));
+ doc_root->updateRepr();
+ Inkscape::GC::release(rdoc);
+
+ ui_root = SP_ITEM(desktop->currentLayer()->appendChildRepr(rui));
+ ui_root->doWriteTransform(ui2doc(), nullptr, true);
+ Inkscape::GC::release(rui);
+}
+
+void API::finish_frame() {
+ doc_root->updateRepr();
+ ui_root->updateRepr();
+ _mouse.finish_frame();
+}
+
+void API::draw_doc(Glib::ustring const &id, Inkscape::XML::Node *repr) {
+ repr->setAttribute("inkscape:livecode-id", id);
+ doc_root->appendChildRepr(repr);
+ Inkscape::GC::release(repr);
+}
+void API::draw_ui(Glib::ustring const &id, Inkscape::XML::Node *repr) {
+ repr->setAttribute("inkscape:livecode-id", id);
+ ui_root->appendChildRepr(repr);
+ Inkscape::GC::release(repr);
+}
+
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livecode/api.h b/src/livecode/api.h
new file mode 100644
index 000000000..d2974a01d
--- /dev/null
+++ b/src/livecode/api.h
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_LIVECODE_API_H
+#define INK_LIVECODE_API_H
+
+/*
+ * API for the livecoding script language
+ *
+ * Authors:
+ * Sol Bekic <s+inkscape@s-ol.nu>
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstddef>
+#include <2geom/point.h>
+#include <2geom/rect.h>
+#include <gdk/gdk.h>
+
+#include "xml/repr.h"
+#include "desktop.h"
+#include "livecode/input.h"
+
+
+class SPDocument;
+class SPItem;
+
+namespace Inkscape {
+namespace Livecode {
+
+class Mouse;
+
+class API {
+public:
+ API(SPDesktop *desktop);
+ ~API();
+
+ Inkscape::XML::Node *make_rect(Geom::Rect const &rect, SPCSSAttr *css = nullptr);
+ Inkscape::XML::Node *make_line(Geom::Point const &p1, Geom::Point const &p2, SPCSSAttr *css = nullptr);
+ Inkscape::XML::Node *make_path(Glib::ustring d, Geom::Point const &pos = Geom::Point(), SPCSSAttr *css = nullptr);
+ Inkscape::XML::Node *make_arrow(Geom::Point const &from, Geom::Point const &to, SPCSSAttr *css = nullptr);
+
+ bool input_point(Glib::ustring const &id, Geom::Point *point);
+ bool input_line(Glib::ustring const &id, Geom::Point *p1, Geom::Point *p2);
+ bool input_arrow(Glib::ustring const &id, Geom::Point *from, Geom::Point *to);
+ bool input_rect(Glib::ustring const &id, Geom::Rect *rect);
+
+ void draw_doc(Glib::ustring const &id, Inkscape::XML::Node *item);
+ void draw_ui(Glib::ustring const &id, Inkscape::XML::Node *item);
+
+ void push_event(GdkEvent *event);
+ void setup_frame();
+ void finish_frame();
+
+ inline Geom::Affine ui2dt() const {
+ return doc_root->i2doc_affine();
+ }
+ inline Geom::Affine dt2ui() const {
+ return ui2dt().inverse();
+ }
+
+ inline Geom::Affine ui2doc() const {
+ return desktop->w2d();
+ }
+ inline Geom::Affine doc2ui() const {
+ return ui2doc().inverse();
+ }
+ inline Geom::Affine ui2doc_vec() const {
+ return desktop->w2d().withoutTranslation();
+ }
+ inline Geom::Affine doc2ui_vec() const {
+ return ui2doc_vec().inverse();
+ }
+
+ Mouse const &mouse();
+
+private:
+ SPDesktop *desktop;
+ SPItem *doc_root, *ui_root;
+
+ Mouse _mouse;
+
+ Glib::ustring hot, active;
+ std::vector<SPItem *> drawn_items;
+};
+
+}
+}
+
+#endif // INK_LIVECODE_API_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livecode/input.cpp b/src/livecode/input.cpp
new file mode 100644
index 000000000..4dab43fc9
--- /dev/null
+++ b/src/livecode/input.cpp
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Input state keeper for the livecoding tool
+ *
+ * Authors:
+ * Sol Bekic <s+inkscape@s-ol.nu>
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "livecode/input.h"
+#include "livecode/api.h"
+#include "desktop.h"
+
+namespace Inkscape {
+namespace Livecode {
+
+
+Mouse::Mouse(API &api)
+ : api(api)
+{
+}
+
+void Mouse::finish_frame()
+{
+ prev_mask = last_mask;
+ prev_pos = last_pos;
+}
+
+void Mouse::push_event(GdkEvent *event)
+{
+ switch (event->type) {
+ case GDK_BUTTON_PRESS: {
+ last_mask |= 1 << event->button.button - 1;
+ break;
+ }
+ case GDK_BUTTON_RELEASE: {
+ last_mask &= ~(1 << event->button.button - 1);
+ break;
+ }
+ case GDK_MOTION_NOTIFY: {
+ last_pos = Geom::Point(event->motion.x, event->motion.y) * api.dt2ui();
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+InputState Mouse::event(guint button) const {
+ guint const flag = 1 << button - 1;
+ bool last = last_mask & flag;
+ bool prev = prev_mask & flag;
+
+ if (last && prev) return INPUTSTATE_HELD;
+ else if (last) return INPUTSTATE_PRESSED;
+ else if (prev) return INPUTSTATE_RELEASED;
+ return INPUTSTATE_NONE;
+}
+
+bool Mouse::pressed(guint button) const {
+ guint const flag = 1 << button - 1;
+ return (last_mask & flag) && !(prev_mask & flag);
+}
+
+bool Mouse::held(guint button) const {
+ return last_mask & (1 << button -1);
+}
+
+bool Mouse::released(guint button) const {
+ guint const flag = 1 << button - 1;
+ return (prev_mask & flag) && !(last_mask & flag);
+}
+
+Geom::Point Mouse::pos() const {
+ return ui_pos() * api.ui2doc();
+}
+Geom::Point Mouse::delta() const {
+ return ui_delta() * api.ui2doc_vec();
+}
+
+Geom::Point Mouse::ui_pos() const {
+ return last_pos;
+}
+Geom::Point Mouse::ui_delta() const {
+ return last_pos - prev_pos;
+}
+
+
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/livecode/input.h b/src/livecode/input.h
new file mode 100644
index 000000000..0b9977af8
--- /dev/null
+++ b/src/livecode/input.h
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_LIVECODE_INPUT_H
+#define INK_LIVECODE_INPUT_H
+
+/*
+ * Input state keeper for the livecoding tool
+ *
+ * Authors:
+ * Sol Bekic <s+inkscape@s-ol.nu>
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gdk/gdk.h>
+#include <2geom/point.h>
+#include <2geom/geom.h>
+
+
+namespace Inkscape {
+namespace Livecode {
+
+class API;
+
+enum InputState {
+ INPUTSTATE_NONE,
+ INPUTSTATE_HOVER,
+ INPUTSTATE_PRESSED,
+ INPUTSTATE_HELD,
+ INPUTSTATE_RELEASED,
+};
+
+class Mouse {
+public:
+ Geom::Point pos() const;
+ Geom::Point delta() const;
+
+ Geom::Point ui_pos() const;
+ Geom::Point ui_delta() const;
+
+ InputState event(guint button = 1) const;
+ bool pressed(guint button = 1) const;
+ bool held(guint button = 1) const;
+ bool released(guint button = 1) const;
+
+private:
+ API& api;
+
+ guint last_mask, prev_mask;
+ Geom::Point last_pos, prev_pos;
+
+ Mouse(API &api);
+
+ void push_event(GdkEvent *event);
+ void finish_frame();
+
+ friend class API;
+};
+
+}
+}
+
+#endif // INK_LIVECODE_INPUT_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :