diff options
Diffstat (limited to 'src/livecode')
| -rw-r--r-- | src/livecode/CMakeLists.txt | 12 | ||||
| -rw-r--r-- | src/livecode/api.cpp | 289 | ||||
| -rw-r--r-- | src/livecode/api.h | 101 | ||||
| -rw-r--r-- | src/livecode/input.cpp | 103 | ||||
| -rw-r--r-- | src/livecode/input.h | 74 |
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 : |
