From 0443c05c744f14d893c4fd931138f666731b587c Mon Sep 17 00:00:00 2001 From: Paul Manias Date: Sun, 20 Oct 2024 13:48:57 +0100 Subject: [PATCH 1/4] [VectorText] Added support for 'OnChange' event; [Document] Widgets now store their values as global variables --- docs/xml/modules/classes/document.xml | 14 ++++---- include/parasol/modules/vector.h | 3 ++ include/parasol/system/fields.h | 1 + src/core/defs/fields.fdl | 1 + src/document/class/document_class.cpp | 13 +++++-- src/document/defs/module_def.c | 2 +- src/document/draw.cpp | 10 +++--- src/document/parsing.cpp | 6 ++++ src/document/ui.cpp | 27 ++++++++++++-- src/vector/vector.fdl | 3 ++ src/vector/vectors/text.cpp | 52 +++++++++++++++++++++++++++ 11 files changed, 115 insertions(+), 17 deletions(-) diff --git a/docs/xml/modules/classes/document.xml b/docs/xml/modules/classes/document.xml index 22404119b..41acdeaed 100644 --- a/docs/xml/modules/classes/document.xml +++ b/docs/xml/modules/classes/document.xml @@ -16,10 +16,8 @@ Paul Manias © 2005-2024

The Document class offers a complete page layout engine, providing rich text display features for creating complex documents and text-based interfaces. Internally, document data is maintained as a serial byte stream and all object model information from the source is discarded. This simplification of the data makes it possible to edit the document in-place, much the same as any word processor. Alternatively it can be used for presentation purposes only, similarly to PDF or HTML formats. Presentation is achieved by building a vector scene graph in conjunction with the Vector module. This means that the output is compatible with SVG and can be manipulated in detail with our existing vector API. Consequently, document formatting is closely integrated with SVG concepts and seamlessly inherits SVG functionality such as filling and stroking commands.

-
Safety
-

The Document class is intended to be safe to use when loading content from an unknown source. Processing will be aborted if a problem is found or the document appears to be unrenderable. It is however, not guaranteed that exploits are impossible. Consideration should also be given to the possibility of exploits that target third party libraries such as libpng and libjpeg for instance.

-

By default, script execution is not enabled when parsing a document source. If support for scripts is enabled, there is no meaningful level of safety on offer when the document is processed. This feature should not be used unless the source document has been written by the client, or has otherwise been received from a trusted source.

-

To mitigate security problems, we recommend that the application is built with some form of sandbox that will stop the system being compromised by bad actors. Utilising a project such as Win32 App Isolation https://github.com/microsoft/win32-app-isolation is one potential way of doing this.

+

The native document format in Parasol is RIPL. Documentation for RIPL is available in the Parasol Wiki. Other document formats may be supported as sub-classes, but bear in mind that document parsing is a one-way trip and stateful information such as the HTML DOM is not supported.

+

The Document class does not include a security barrier in its current form. Documents that include scripted code should not be processed unless they originate from a trusted source and are confirmed as such. To mitigate security problems, we recommend that the application is built with some form of sandbox that can prevent the system being compromised by bad actors. Utilising a project such as Win32 App Isolation https://github.com/microsoft/win32-app-isolation is one potential way of doing this.

document_class.cpp fields.cpp @@ -107,13 +105,17 @@ GetKey - Retrieves script parameters. + Retrieves global variables and URI parameters. ERR acGetKey(*Object, CSTRING Key, STRING Value, LONG Size) The name of a key value. Pointer to a buffer space large enough to hold the retrieved value. Indicates the byte size of the Buffer. + +

Use GetKey() to access the global variables and URI parameters of a document. Priority is given to global variables if there is a name clash.

+

The current value of each document widget is also available as a global variable accessible from GetKey(). The key-value will be given the same name as that specified in the widget's element.

+
@@ -134,7 +136,7 @@ SetKey - Passes variable parameters to loaded documents. + Set a global key-value in the document. ERR acSetKey(*Object, CSTRING Key, CSTRING Value) The name of the target key. diff --git a/include/parasol/modules/vector.h b/include/parasol/modules/vector.h index bc2d86134..8a4252734 100644 --- a/include/parasol/modules/vector.h +++ b/include/parasol/modules/vector.h @@ -3946,6 +3946,9 @@ constexpr FieldValue AppendPath(OBJECTPTR Value) { return FieldValue(FID_AppendP constexpr FieldValue DragCallback(const FUNCTION &Value) { return FieldValue(FID_DragCallback, &Value); } constexpr FieldValue DragCallback(const FUNCTION *Value) { return FieldValue(FID_DragCallback, Value); } +constexpr FieldValue OnChange(const FUNCTION &Value) { return FieldValue(FID_OnChange, &Value); } +constexpr FieldValue OnChange(const FUNCTION *Value) { return FieldValue(FID_OnChange, Value); } + constexpr FieldValue TextFlags(VTXF Value) { return FieldValue(FID_TextFlags, LONG(Value)); } constexpr FieldValue Overflow(VOF Value) { return FieldValue(FID_Overflow, LONG(Value)); } diff --git a/include/parasol/system/fields.h b/include/parasol/system/fields.h index e0949385a..f59bbf320 100644 --- a/include/parasol/system/fields.h +++ b/include/parasol/system/fields.h @@ -934,6 +934,7 @@ #define FID_MatrixRows 0x64419145LL #define FID_MatrixColumns 0x54dc215bLL #define FID_Matrix 0x0d3e291aLL +#define FID_OnChange 0xee446f88LL #define FID_PathTimestamp 0x2efda9a6LL #define FID_PreserveAlpha 0xf9b49d57LL #define FID_Pretext 0xc28b4df1LL diff --git a/src/core/defs/fields.fdl b/src/core/defs/fields.fdl index 9442b016a..93fb7c93c 100644 --- a/src/core/defs/fields.fdl +++ b/src/core/defs/fields.fdl @@ -941,6 +941,7 @@ header({ path="system/fields", copyright="Paul Manias © 1996-2024" }, function( "MatrixRows", "MatrixColumns", "Matrix", + "OnChange", "PathTimestamp", "PreserveAlpha", "Pretext", diff --git a/src/document/class/document_class.cpp b/src/document/class/document_class.cpp index 2015e424a..cbb0d7ade 100644 --- a/src/document/class/document_class.cpp +++ b/src/document/class/document_class.cpp @@ -642,7 +642,14 @@ static ERR DOCUMENT_Free(extDocument *Self) /********************************************************************************************************************* -ACTION- -GetKey: Retrieves script parameters. +GetKey: Retrieves global variables and URI parameters. + +Use GetKey() to access the global variables and URI parameters of a document. Priority is given to global +variables if there is a name clash. + +The current value of each document widget is also available as a global variable accessible from GetKey(). The +key-value will be given the same name as that specified in the widget's element. + -END- *********************************************************************************************************************/ @@ -1246,13 +1253,13 @@ static ERR DOCUMENT_SelectLink(extDocument *Self, struct doc::SelectLink *Args) /********************************************************************************************************************* -ACTION- -SetKey: Passes variable parameters to loaded documents. +SetKey: Set a global key-value in the document. -END- *********************************************************************************************************************/ static ERR DOCUMENT_SetKey(extDocument *Self, struct acSetKey *Args) { - // Please note that it is okay to set zero-length parameters + // Note: Zero-length parameter values are permitted. if ((!Args) or (!Args->Key)) return ERR::NullArgs; if (!Args->Key[0]) return ERR::Args; diff --git a/src/document/defs/module_def.c b/src/document/defs/module_def.c index 532598b87..7b98b37bf 100644 --- a/src/document/defs/module_def.c +++ b/src/document/defs/module_def.c @@ -1,4 +1,4 @@ // Auto-generated by idl-c.fluid #undef MOD_IDL -#define MOD_IDL "c.DCF:DISABLED=0x8,EDIT=0x1,NO_LAYOUT_MSG=0x10,NO_SYS_KEYS=0x4,OVERWRITE=0x2,UNRESTRICTED=0x20\nc.DEF:LINK_ACTIVATED=0x20,ON_CLICK=0x2,ON_CROSSING=0x18,ON_CROSSING_IN=0x8,ON_CROSSING_OUT=0x10,ON_MOTION=0x4,PATH=0x1\nc.DRT:AFTER_LAYOUT=0x1,BEFORE_LAYOUT=0x0,END=0xa,GOT_FOCUS=0x6,LEAVING_PAGE=0x8,LOST_FOCUS=0x7,PAGE_PROCESSED=0x9,REFRESH=0x5,USER_CLICK=0x2,USER_CLICK_RELEASE=0x3,USER_MOVEMENT=0x4\nc.FSO:ALIGN_CENTER=0x20,ALIGN_RIGHT=0x10,BOLD=0x1,ITALIC=0x2,NO_WRAP=0x40,PREFORMAT=0x8,STYLES=0x7,UNDERLINE=0x4\nc.RIPL:VERSION=""20240126""\nc.TT:EDIT=0x3,LINK=0x2,VECTOR=0x1\n" +#define MOD_IDL "c.DCF:DISABLED=0x8,EDIT=0x1,NO_LAYOUT_MSG=0x10,NO_SYS_KEYS=0x4,OVERWRITE=0x2,UNRESTRICTED=0x20\nc.DEF:LINK_ACTIVATED=0x20,ON_CLICK=0x2,ON_CROSSING=0x18,ON_CROSSING_IN=0x8,ON_CROSSING_OUT=0x10,ON_MOTION=0x4,PATH=0x1,WIDGET_STATE=0x40\nc.DRT:AFTER_LAYOUT=0x1,BEFORE_LAYOUT=0x0,END=0xa,GOT_FOCUS=0x6,LEAVING_PAGE=0x8,LOST_FOCUS=0x7,PAGE_PROCESSED=0x9,REFRESH=0x5,USER_CLICK=0x2,USER_CLICK_RELEASE=0x3,USER_MOVEMENT=0x4\nc.FSO:ALIGN_CENTER=0x20,ALIGN_RIGHT=0x10,BOLD=0x1,ITALIC=0x2,NO_WRAP=0x40,PREFORMAT=0x8,STYLES=0x7,UNDERLINE=0x4\nc.RIPL:VERSION=""20240126""\nc.TT:EDIT=0x3,LINK=0x2,VECTOR=0x1\n" diff --git a/src/document/draw.cpp b/src/document/draw.cpp index 10aa4205a..9c4df8dce 100644 --- a/src/document/draw.cpp +++ b/src/document/draw.cpp @@ -649,7 +649,7 @@ void layout::gen_scene_graph(objVectorViewport *Viewport, std::vectormetrics.Height) * 0.5); combo.input = objVectorText::create::global({ - fl::Name("combo_input"), + fl::Name(combo.name.empty() ? "combo_input" : combo.name), // Required for notify_input_onchange() fl::Owner(combo.clip_vp->UID), fl::X(0), fl::Y(F2T(y)), fl::String(combo.value), @@ -659,7 +659,8 @@ void layout::gen_scene_graph(objVectorViewport *Viewport, std::vectorstyle), fl::Fill(combo.font_fill), fl::LineLimit(1), - fl::TextFlags(VTXF::EDITABLE) + fl::TextFlags(VTXF::EDITABLE), + fl::OnChange(C_FUNCTION(notify_input_onchange)) }); } @@ -750,7 +751,7 @@ void layout::gen_scene_graph(objVectorViewport *Viewport, std::vectormetrics.Height) * 0.5); objVectorText::create::global({ - fl::Name("input_text"), + fl::Name(input.name.empty() ? "input_text" : input.name), // Required for notify_input_onchange() fl::Owner(input.clip_vp->UID), fl::X(0), fl::Y(F2T(y)), fl::String(input.value), @@ -760,7 +761,8 @@ void layout::gen_scene_graph(objVectorViewport *Viewport, std::vectorstyle), fl::Fill(input.font_fill), fl::LineLimit(1), - fl::TextFlags(flags) + fl::TextFlags(flags), + fl::OnChange(C_FUNCTION(notify_input_onchange)) }); } diff --git a/src/document/parsing.cpp b/src/document/parsing.cpp index 11caaf43f..1a40018ef 100644 --- a/src/document/parsing.cpp +++ b/src/document/parsing.cpp @@ -1861,6 +1861,8 @@ void parser::tag_checkbox(XMLTag &Tag) if (!widget.label.empty()) widget.label_pad = m_style.get_font()->metrics.Ascent * 0.5; + if (!widget.name.empty()) Self->Vars[widget.name] = widget.alt_state ? "1" : "0"; + Self->NoWhitespace = false; // Widgets are treated as inline characters } @@ -1978,6 +1980,8 @@ void parser::tag_combobox(XMLTag &Tag) widget.def_size = DUNIT(1.7, DU::FONT_SIZE); widget.label_pad = m_style.get_font()->metrics.Ascent * 0.5; + if (!widget.name.empty()) Self->Vars[widget.name] = widget.value; + Self->NoWhitespace = false; // Widgets are treated as inline characters } @@ -2018,6 +2022,8 @@ void parser::tag_input(XMLTag &Tag) widget.def_size = DUNIT(1.7, DU::FONT_SIZE); widget.label_pad = m_style.get_font()->metrics.Ascent * 0.5; + if (!widget.name.empty()) Self->Vars[widget.name] = widget.value; + Self->NoWhitespace = false; // Widgets are treated as inline characters } diff --git a/src/document/ui.cpp b/src/document/ui.cpp index b7d9d0786..c6d928964 100644 --- a/src/document/ui.cpp +++ b/src/document/ui.cpp @@ -1,4 +1,12 @@ +static void notify_input_onchange(objVectorText *Vector) +{ + auto Self = (extDocument *)CurrentContext(); + auto str = Vector->get(FID_String); + + Self->Vars[Vector->Name].assign(str); +} + //******************************************************************************************************************** // Feedback events for the combobox viewport. Note that the viewport retains focus when the drop-down list is // presented. @@ -19,6 +27,7 @@ static ERR combo_feedback(objVectorViewport *Viewport, FM Event, OBJECTPTR Event } else if ((Event IS FM::HAS_FOCUS) or (Event IS FM::CHILD_HAS_FOCUS)) { combo->last_good_input = combo->input->get(FID_String); + if (!combo->name.empty()) Self->Vars[combo->name] = combo->last_good_input; } Viewport->draw(); @@ -30,10 +39,17 @@ static ERR combo_feedback(objVectorViewport *Viewport, FM Event, OBJECTPTR Event void bc_combobox::callback(struct doc_menu &Menu, struct dropdown_item &Item) { + auto Self = (extDocument *)CurrentContext(); auto combo = std::get(Menu.m_ref); if (combo) { - if (!Item.value.empty()) combo->input->setFields(fl::String(Item.value)); - else combo->input->setFields(fl::String(Item.content)); + if (!Item.value.empty()) { + combo->input->setFields(fl::String(Item.value)); + if (!combo->name.empty()) Self->Vars[combo->name] = Item.value; + } + else { + combo->input->setFields(fl::String(Item.content)); + if (!combo->name.empty()) Self->Vars[combo->name] = Item.content; + } combo->viewport->draw(); } } @@ -1279,7 +1295,12 @@ static ERR inputevent_checkbox(objVectorViewport *Viewport, const InputEvent *Ev for (; Event; Event = Event->Next) { if ((Event->Flags & JTYPE::BUTTON) != JTYPE::NIL) { if (Event->Type IS JET::LMB) { - if (Event->Value IS 1) checkbox->alt_state ^= 1; + if (Event->Value IS 1) { + checkbox->alt_state ^= 1; + if (!checkbox->name.empty()) { + Self->Vars[checkbox->name] = checkbox->alt_state ? "1" : "0"; + } + } } if (checkbox->alt_state) checkbox->viewport->setFill(checkbox->alt_fill); diff --git a/src/vector/vector.fdl b/src/vector/vector.fdl index 3282e3ef7..059cb70c0 100644 --- a/src/vector/vector.fdl +++ b/src/vector/vector.fdl @@ -1206,6 +1206,9 @@ constexpr FieldValue AppendPath(OBJECTPTR Value) { return FieldValue(FID_AppendP constexpr FieldValue DragCallback(const FUNCTION &Value) { return FieldValue(FID_DragCallback, &Value); } constexpr FieldValue DragCallback(const FUNCTION *Value) { return FieldValue(FID_DragCallback, Value); } +constexpr FieldValue OnChange(const FUNCTION &Value) { return FieldValue(FID_OnChange, &Value); } +constexpr FieldValue OnChange(const FUNCTION *Value) { return FieldValue(FID_OnChange, Value); } + constexpr FieldValue TextFlags(VTXF Value) { return FieldValue(FID_TextFlags, LONG(Value)); } constexpr FieldValue Overflow(VOF Value) { return FieldValue(FID_Overflow, LONG(Value)); } diff --git a/src/vector/vectors/text.cpp b/src/vector/vectors/text.cpp index b7c6008dd..2f4b44dee 100644 --- a/src/vector/vectors/text.cpp +++ b/src/vector/vectors/text.cpp @@ -175,6 +175,7 @@ class extVectorText : public extVector { std::vector txLines; FUNCTION txValidateInput; + FUNCTION txOnChange; DOUBLE txInlineSize; // Enables word-wrapping DOUBLE txX, txY; DOUBLE txTextLength; @@ -252,6 +253,20 @@ inline DOUBLE get_kerning(FT_Face Face, LONG Glyph, LONG PrevGlyph) return int26p6_to_dbl(delta.x); } +inline void report_change(extVectorText *Self) +{ + if (Self->txOnChange.isC()) { + auto routine = (void (*)(extVectorText *))Self->txOnChange.Routine; + pf::SwitchContext context(Self->txOnChange.Context); + routine(Self); + } + else if (Self->txOnChange.isScript()) { + sc::Call(Self->txOnChange, std::to_array({ + { "VectorText", Self, FD_OBJECTPTR } + })); + } +} + //******************************************************************************************************************** static LONG string_width(extVectorText *Self, const std::string_view &String) @@ -651,6 +666,36 @@ static ERR TEXT_SET_DY(extVectorText *Self, DOUBLE *Values, LONG Elements) else return ERR::Okay; } +/********************************************************************************************************************* + +-FIELD- +OnChange: Receive notifications for changes to the text string. + +Set this field with a function reference to receive notifications whenever the text string changes. + +The callback function prototype is `void Function(*VectorText)`. + +*********************************************************************************************************************/ + +static ERR TEXT_GET_OnChange(extVectorText *Self, FUNCTION **Value) +{ + if (Self->txOnChange.defined()) { + *Value = &Self->txOnChange; + return ERR::Okay; + } + else return ERR::FieldNotSet; +} + +static ERR TEXT_SET_OnChange(extVectorText *Self, FUNCTION *Value) +{ + if (Value) { + if (Self->txOnChange.isScript()) UnsubscribeAction(Self->txOnChange.Context, AC::Free); + Self->txOnChange = *Value; + } + else Self->txOnChange.clear(); + return ERR::Okay; +} + /********************************************************************************************************************* -FIELD- Face: Defines the font face/family to use in rendering the text string. @@ -1686,6 +1731,7 @@ static void key_event(extVectorText *Self, evKey *Event, LONG Size) mark_dirty(Self, RC::BASE_PATH); acDraw(Self); + report_change(Self); break; case KEY::CLEAR: @@ -1694,6 +1740,7 @@ static void key_event(extVectorText *Self, evKey *Event, LONG Size) Self->txCursor.move(Self, Self->txCursor.row(), 0); ((objVectorText *)Self)->deleteLine(Self->txCursor.row()); } + report_change(Self); break; case KEY::DELETE: @@ -1708,6 +1755,7 @@ static void key_event(extVectorText *Self, evKey *Event, LONG Size) } mark_dirty(Self, RC::BASE_PATH); acDraw(Self); + report_change(Self); break; case KEY::END: @@ -1733,6 +1781,7 @@ static void key_event(extVectorText *Self, evKey *Event, LONG Size) else Self->txLines[row].replace(offset, Self->txLines[row].length() - offset, ""); mark_dirty(Self, RC::BASE_PATH); acDraw(Self); + report_change(Self); break; } @@ -1979,6 +2028,8 @@ static void insert_char(extVectorText *Self, LONG Unicode, LONG Column) Self->txCursor.move(Self, Self->txCursor.row(), Self->txCursor.column() + 1, true); } + + report_change(Self); } //******************************************************************************************************************** @@ -2027,6 +2078,7 @@ static const FieldArray clTextFields[] = { { "Spacing", FDF_VIRTUAL|FDF_DOUBLE|FDF_RW, TEXT_GET_Spacing, TEXT_SET_Spacing }, { "Font", FDF_VIRTUAL|FDF_OBJECT|FDF_I, NULL, TEXT_SET_Font }, // Non-SVG fields related to real-time text editing + { "OnChange", FDF_VIRTUAL|FDF_FUNCTIONPTR|FDF_RW, TEXT_GET_OnChange, TEXT_SET_OnChange }, { "Focus", FDF_VIRTUAL|FDF_OBJECTID|FDF_RI, TEXT_GET_Focus, TEXT_SET_Focus }, { "CursorColumn", FDF_VIRTUAL|FDF_LONG|FDF_RW, TEXT_GET_CursorColumn, TEXT_SET_CursorColumn }, { "CursorRow", FDF_VIRTUAL|FDF_LONG|FDF_RW, TEXT_GET_CursorRow, TEXT_SET_CursorRow }, From 196b9eb8306bc462e655f46a0a28673d8d807913 Mon Sep 17 00:00:00 2001 From: Paul Manias Date: Sun, 20 Oct 2024 14:35:32 +0100 Subject: [PATCH 2/4] [Dialog] Fixed support for input and checkbox widgets --- scripts/gui/dialog.fluid | 43 ++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/scripts/gui/dialog.fluid b/scripts/gui/dialog.fluid index fe5a4732c..ec0491499 100644 --- a/scripts/gui/dialog.fluid +++ b/scripts/gui/dialog.fluid @@ -31,7 +31,7 @@ local function defaultIcon(self) end gui.dialog.message = function(Options) - local self = { // Public variables + local self = { -- Public variables document = nil, options = Options.options, type = Options.type, @@ -83,20 +83,18 @@ gui.dialog.message = function(Options) local response = self.options[responseIndex] local state = { } - if self.checkbox then - if self.checkbox.value == 1 then - state.checkboxState = true - else - state.checkboxState = false - end + if Document.getKey('checkbox') == '1' then + state.checkbox = true + else + state.checkbox = false end - if self.input then - state.input = nz(self.input.string, nil) - if self.inputRequired and not state.input then - // If input is mandatory, the response is cancelled when no input is given - response = nil - end + local input = Document.getKey('input') + if nz(input) then state.input = input end + + if self.inputRequired and not state.input then + -- If input is mandatory, the response is cancelled when no input is given + response = nil end self.feedback(self, response, state) @@ -111,7 +109,7 @@ gui.dialog.message = function(Options) local function buildDocument() local option_icons = '' - // At minimum, there must be an OK option. + -- At minimum, there must be an OK option. if not ((type(self.options) == 'table') and (#self.options > 0)) then self.options = { { id=1, text='Close Dialog', icon='items/cancel' } } @@ -138,13 +136,13 @@ gui.dialog.message = function(Options) ]] if Options.envTemplate then - // The EnvTemplate can redefine the default body, GUI templates etc + -- The EnvTemplate can redefine the default body, GUI templates etc doc = doc .. options.envTemplate .. '\n' end if Options.template then - // A dialog-specific template can override the body style and change any class templates, - // header and footer etc + -- A dialog-specific template can override the body style and change any class templates, + -- header and footer etc doc = doc .. '\n' end @@ -178,13 +176,13 @@ gui.dialog.message = function(Options) msg = '' end - doc = doc .. '

\n' + doc = doc .. '

\n' end if Options.checkboxLabel then local checkbox_value = 'false' if Options.checkboxState then checkbox_value = 'true' end - doc = doc .. '

\n' + doc = doc .. '

\n' end if Options.inject then @@ -267,15 +265,12 @@ gui.dialog.message = function(Options) eventMask = 'LinkActivated', eventCallback = docEvent, path = '#Index', - flags = 'Unrestricted|NoScrollbars', + flags = 'NoScrollbars', clientScript = obj.find('self') }) lDoc.acDataFeed(0, DATA_XML, buildDocument()) - self.input = obj.find('inDialog') - self.checkbox = obj.find('dlgCheckbox') - if self.input then self.input.textinput.mtSelectArea(0, 0, 20000, 20000) end @@ -290,7 +285,7 @@ gui.dialog.message = function(Options) return self end - // This sub-routine is provided for languages other than Fluid to utilise the module. + -- This sub-routine is provided for languages other than Fluid to utilise the module. do local state = getExecutionState() From b4c1c9ca70ee08395da83df78c2533a9139e9c37 Mon Sep 17 00:00:00 2001 From: Paul Manias Date: Sun, 20 Oct 2024 19:33:29 +0100 Subject: [PATCH 3/4] [Document] Added support for widget state events. --- docs/html/modules/classes/document.html | 15 ++++--- docs/html/modules/core.html | 2 +- docs/xml/modules/classes/document.xml | 1 + include/parasol/modules/document.h | 1 + src/document/class/document_def.c | 1 + src/document/defs/document.fdl | 1 + src/document/draw.cpp | 15 ++++--- src/document/ui.cpp | 57 ++++++++++++++++++++++--- 8 files changed, 72 insertions(+), 21 deletions(-) diff --git a/docs/html/modules/classes/document.html b/docs/html/modules/classes/document.html index e3f34e6d6..a78aee9b9 100644 --- a/docs/html/modules/classes/document.html +++ b/docs/html/modules/classes/document.html @@ -9,10 +9,8 @@ function load() { if (window.location.hash) shiftWindow(); }