// SPDX-License-Identifier: GPL-2.0-or-later /* * Context for the livecoding script language * * Authors: * Sol Bekic * Copyright (C) 2019 Authors * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include #include "svg/stringstream.h" #include "svg/svg-color.h" #include "svg/svg.h" #include "document.h" #include "display/sp-canvas-item.h" #include "livecode/context.h" #include "livecode/script.h" #include "livecode/api/api.h" namespace Inkscape { namespace Livecode { Context::Context(SPDesktop *desktop) : desktop(desktop) , _mouse(*this) , doc_root(nullptr) , ui_root(nullptr) { janet_init(); env = janet_core_env(NULL); janet_lib_context(env, *this); janet_lib_input(env); janet_lib_geom_point(env); } Context::~Context() { janet_deinit(); 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 *Context::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 *Context::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 *Context::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 *Context::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() * 5.0 + back * 10.0; Geom::Point const cross_b = back.ccw() * 5.0 + back * 10.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); 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; } 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 Context::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 Context::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; } SPCSSAttr *css = sp_repr_css_attr_new(); sp_repr_css_set_property(css, "stroke-width", "1"); sp_repr_css_set_property(css, "stroke", "#000000"); sp_repr_css_set_property(css, "fill", "none"); draw_doc("", make_line(*p1, *p2, css)); return change; } bool Context::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; } SPCSSAttr *css = sp_repr_css_attr_new(); sp_repr_css_set_property(css, "stroke-width", "1"); sp_repr_css_set_property(css, "stroke", "#000000"); sp_repr_css_set_property(css, "fill", "none"); draw_doc("", make_arrow(*from, *to, css)); return change; } bool Context::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 &Context::mouse() { return _mouse; } void Context::push_event(GdkEvent *event) { _mouse.push_event(event); } void Context::frame() { setup_frame(); if (_script) { _script->frame(); } finish_frame(); } void Context::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 Context::finish_frame() { doc_root->updateRepr(); ui_root->updateRepr(); _mouse.finish_frame(); } void Context::load_script(Glib::ustring const &path) { g_message("loading script %s", path.c_str()); _script = nullptr; try { _script.reset(new Script(env, path)); g_message("loaded."); } catch (...) { g_message("error creating script"); } } void Context::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 Context::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 :