diff --git a/GUIDE.md b/GUIDE.md index eaded985..c8ea8e71 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -402,8 +402,20 @@ this.setOutputData(0, { }); ``` +### Event Bus +TODO +### Progress / highlight +TODO +### Dom Anchors +TODO + +### DOMWidget +TODO + +### Theme +TODO diff --git a/apps/editor/src/main.ts b/apps/editor/src/main.ts index 3b7ec0aa..7d5424c3 100644 --- a/apps/editor/src/main.ts +++ b/apps/editor/src/main.ts @@ -1,4 +1,4 @@ -import { LiteGraph } from "@litegraph-ts/core"; +import { LGraphTheme, LiteGraph } from "@litegraph-ts/core"; import Editor from "./Editor"; import configure from "./configure"; @@ -27,6 +27,14 @@ editor.graphCanvas.pause_rendering = false; window["editor"] = editor; window["LiteGraph"] = LiteGraph; +window["LGraphTheme"] = LGraphTheme; +window["apply_theme"] = (theme: any) => { + const t = new LGraphTheme(theme); + t.apply({ + graph: editor.graph, + canvas: editor.graphCanvas, + }); +}; window.addEventListener("resize", () => { editor.graphCanvas.resize(); diff --git a/packages/core/css/litegraph.css b/packages/core/css/litegraph.css index b7303bf9..8332e86e 100644 --- a/packages/core/css/litegraph.css +++ b/packages/core/css/litegraph.css @@ -19,15 +19,15 @@ top: 100px; left: 100px; min-width: 100px; - color: #aaf; + color: var(--lg_menu_fg, #aaf); padding: 0; - box-shadow: 0 0 10px black !important; - background-color: #2e2e2e !important; + box-shadow: var(--lg_menu_shadow,0 0 10px black) !important; + background-color: var(--lg_menu_bg,#2e2e2e) !important; z-index: 10; } .litegraph.litecontextmenu.dark { - background-color: #000 !important; + background-color: var(--lg_menu_bg_dark, #000) !important; } .litegraph.litecontextmenu .litemenu-title img { @@ -42,11 +42,11 @@ } .litegraph.litecontextmenu .litemenu-entry.submenu { - background-color: #2e2e2e !important; + background-color: var(--lg_menu_entry_bg,#2e2e2e) !important; } .litegraph.litecontextmenu.dark .litemenu-entry.submenu { - background-color: #000 !important; + background-color: var(--lg_menu_entry_bg_dark,#000) !important; } .litegraph .litemenubar ul { @@ -54,10 +54,9 @@ margin: 0; padding: 0; } - .litegraph .litemenubar li { font-size: 14px; - color: #999; + color: var(--lg_menu_bar_fg, #999); display: inline-block; min-width: 50px; padding-left: 10px; @@ -69,8 +68,8 @@ } .litegraph .litemenubar li:hover { - background-color: #777; - color: #eee; + background-color: var(--lg_menu_bar_hover_bg, #777); + color: var(--lg_menu_bar_hover_fg, #eee); } .litegraph .litegraph .litemenubar-panel { @@ -78,17 +77,17 @@ top: 5px; left: 5px; min-width: 100px; - background-color: #444; - box-shadow: 0 0 3px black; + background-color: var(--lg_menu_panel_bg, #444); + box-shadow: 0 0 3px var(--lg_menu_panel_shadow, black); padding: 4px; - border-bottom: 2px solid #aaf; + border-bottom: 2px solid var(--lg_menu_panel_border, #aaf); z-index: 10; } .litegraph .litemenu-entry, .litemenu-title { font-size: 12px; - color: #aaa; + color: var(--lg_menu_entry_fg, #aaa); padding: 0 0 0 4px; margin: 2px; padding-left: 2px; @@ -107,7 +106,7 @@ } .litegraph .litemenu-entry.checked .icon { - background-color: #aaf; + background-color: var(--lg_menu_entry_checked_bg, #aaf); } .litegraph .litemenu-entry .more { @@ -122,8 +121,8 @@ .litegraph .litemenu-entry.separator { display: block; - border-top: 1px solid #333; - border-bottom: 1px solid #666; + border-top: 1px solid var(--lg_menu_entry_separator_top, #333); + border-bottom: 1px solid var(--lg_menu_entry_separator_bottom, #666); width: 100%; height: 0px; margin: 3px 0 2px 0; @@ -133,20 +132,20 @@ } .litegraph .litemenu-entry.has_submenu { - border-right: 2px solid cyan; + border-right: 2px solid var(--lg_menu_entry_submenu_border, cyan); } .litegraph .litemenu-title { - color: #dde; - background-color: #111; + color: var(--lg_menu_title_fg, #dde); + background-color: var(--lg_menu_title_bg, #111); margin: 0; padding: 2px; cursor: default; } .litegraph .litemenu-entry:hover:not(.disabled):not(.separator) { - background-color: #444 !important; - color: #eee; + background-color: var(--lg_menu_entry_hover_bg, #444) !important; + color: var(--lg_menu_entry_hover_fg, #eee); transition: all 0.2s; } @@ -159,7 +158,7 @@ .litegraph .litemenu-entry .property_value { display: inline-block; - background-color: rgba(0, 0, 0, 0.5); + background-color: var(--lg_menu_entry_property_value_bg, rgba(0, 0, 0, 0.5)); text-align: right; min-width: 80px; min-height: 1.2em; @@ -170,18 +169,17 @@ .litegraph.litesearchbox { font-family: Tahoma, sans-serif; position: absolute; - background-color: rgba(0, 0, 0, 0.5); + background-color: var(--lg_searchbox_bg, rgba(0, 0, 0, 0.5)); padding-top: 4px; } - .litegraph.litesearchbox input, .litegraph.litesearchbox select { margin-top: 3px; min-width: 60px; min-height: 1.5em; - background-color: black; + background-color: var(--lg_searchbox_input_bg, black); border: 0; - color: white; + color: var(--lg_searchbox_fg, white); padding-left: 10px; margin-right: 5px; } @@ -201,34 +199,29 @@ .litegraph.lite-search-item { font-family: Tahoma, sans-serif; - background-color: rgba(0, 0, 0, 0.5); - color: white; + background-color: var(--lg_searchitem_bg, rgba(0, 0, 0, 0.5)); + color: var(--lg_searchitem_fg, white); padding-top: 2px; } .litegraph.lite-search-item.not_in_filter { - /*background-color: rgba(50, 50, 50, 0.5);*/ - /*color: #999;*/ - color: #b99; + color: var(--lg_searchitem_not_in_filter_fg, #b99); font-style: italic; } .litegraph.lite-search-item.generic_type { - /*background-color: rgba(50, 50, 50, 0.5);*/ - /*color: #DD9;*/ - color: #999; + color: var(--lg_searchitem_generic_type_fg, #999); font-style: italic; } .litegraph.lite-search-item:hover, .litegraph.lite-search-item.selected { cursor: pointer; - background-color: white; - color: black; + background-color: var(--lg_searchitem_hover_bg, white); + color: var(--lg_searchitem_hover_fg, black); } /* DIALOGs ******/ - .litegraph .dialog { position: absolute; top: 50%; @@ -236,11 +229,11 @@ margin-top: -150px; margin-left: -200px; - background-color: #2a2a2a; + background-color: var(--lg_dialog_bg, #2a2a2a); min-width: 400px; min-height: 200px; - box-shadow: 0 0 4px #111; + box-shadow: 0 0 4px var(--lg_dialog_shadow, #111); border-radius: 6px; } @@ -272,12 +265,12 @@ } .litegraph .dialog .close:hover { - color: white; + color: var(--lg_dialog_close_hover, white); } .litegraph .dialog .dialog-header { - color: #aaa; - border-bottom: 1px solid #161616; + color: var(--lg_dialog_header_fg, #aaa); + border-bottom: 1px solid var(--lg_dialog_header_border, #161616); } .litegraph .dialog .dialog-header { @@ -286,7 +279,7 @@ .litegraph .dialog .dialog-footer { height: 50px; padding: 10px; - border-top: 1px solid #1a1a1a; + border-top: 1px solid var(--lg_dialog_footer_border, #1a1a1a); } .litegraph .dialog .dialog-header .dialog-title { @@ -295,15 +288,13 @@ padding: 4px 10px; display: inline-block; } - .litegraph .dialog .dialog-content, .litegraph .dialog .dialog-alt-content { height: calc(100% - 90px); width: 100%; min-height: 100px; display: inline-block; - color: #aaa; - /*background-color: black;*/ + color: var(--lg_dialog_fg, #aaa); overflow: auto; } @@ -318,7 +309,7 @@ .litegraph .dialog .dialog-content .connections .connections_side { width: calc(50% - 5px); min-height: 100px; - background-color: black; + background-color: var(--lg_dialog_side_bg, black); display: flex; } @@ -338,8 +329,8 @@ display: block; width: calc(100% - 4px); height: 1px; - border-top: 1px solid #000; - border-bottom: 1px solid #333; + border-top: 1px solid var(--lg_separator_top, #000); + border-bottom: 1px solid var(--lg_separator_bottom, #333); margin: 10px 2px; padding: 0; } @@ -350,11 +341,11 @@ } .litegraph .dialog .property:hover { - background: #545454; + background: var(--lg_property_hover, #545454); } .litegraph .dialog .property_name { - color: #737373; + color: var(--lg_property_name_fg, #737373); display: inline-block; text-align: left; vertical-align: top; @@ -363,16 +354,15 @@ overflow: hidden; margin-right: 6px; } - .litegraph .dialog .property:hover .property_name { - color: white; + color: var(--lg_dialog_hover_fg, white); } .litegraph .dialog .property_value { display: inline-block; text-align: right; - color: #aaa; - background-color: #1a1a1a; + color: var(--lg_dialog_value_fg, #aaa); + background-color: var(--lg_dialog_value_bg, #1a1a1a); /*width: calc( 100% - 122px );*/ max-width: calc(100% - 162px); min-width: 200px; @@ -386,21 +376,18 @@ } .litegraph .dialog .property_value:hover { - color: white; + color: var(--lg_dialog_value_hover_fg, white); } .litegraph .dialog .property.boolean .property_value { padding-right: 30px; - color: #a88; + color: var(--lg_dialog_bool_fg, #a88); /*width: auto; float: right;*/ } -.litegraph .dialog .property.boolean.bool-on .property_name { - color: #8a8; -} .litegraph .dialog .property.boolean.bool-on .property_value { - color: #8a8; + color: var(--lg_dialog_bool_on_fg, #8a8); } .litegraph .dialog .btn { @@ -408,18 +395,18 @@ border-radius: 4px; padding: 4px 20px; margin-left: 0px; - background-color: #060606; - color: #8e8e8e; + background-color: var(--lg_dialog_btn_bg, #060606); + color: var(--lg_dialog_btn_fg, #8e8e8e); } .litegraph .dialog .btn:hover { - background-color: #111; - color: #fff; + background-color: var(--lg_dialog_btn_hover_bg, #111); + color: var(--lg_dialog_btn_hover_fg, #fff); } .litegraph .dialog .btn.delete:hover { - background-color: #f33; - color: black; + background-color: var(--lg_dialog_btn_delete_hover_bg, #f33); + color: var(--lg_dialog_btn_delete_hover_fg, black); } .litegraph .subgraph_property { @@ -427,7 +414,7 @@ } .litegraph .subgraph_property:hover { - background-color: #333; + background-color: var(--lg_subgraph_hover_bg, #333); } .litegraph .subgraph_property :disabled { @@ -453,7 +440,7 @@ .litegraph .subgraph_property > select.type { margin-right: 20px; padding-left: 4px; - background-color: black; + background-color: var(--lg_subgraph_type_bg, black); border: 0; } @@ -466,17 +453,16 @@ .litegraph .subgraph_property input, .litegraph .subgraph_property select { width: 140px; - color: #ccc; - background-color: #1a1a1a; + color: var(--lg_subgraph_input_fg, #ccc); + background-color: var(--lg_subgraph_input_bg, #1a1a1a); border: 0; margin-right: 10px; padding: 4px; padding-left: 10px; } - .litegraph .subgraph_property button { - background-color: #1c1c1c; - color: #aaa; + background-color: var(--lg_subgraph_property_bg, #1c1c1c); + color: var(--lg_subgraph_property_fg, #aaa); border: 0; border-radius: 2px; padding: 4px 10px; @@ -484,12 +470,12 @@ } .litegraph .subgraph_property.extra { - color: #ccc; + color: var(--lg_subgraph_property_extra_fg, #ccc); } .litegraph .subgraph_property.extra input, .litegraph .subgraph_property.extra select { - background-color: #111; + background-color: var(--lg_subgraph_property_extra_bg, #111); } .litegraph .bullet_icon { @@ -497,7 +483,7 @@ border-radius: 10px; width: 12px; height: 12px; - background-color: #666; + background-color: var(--lg_bullet_icon_bg, #666); display: inline-block; margin-top: 2px; margin-right: 4px; @@ -506,24 +492,22 @@ } .litegraph .bullet_icon:hover { - background-color: #698; + background-color: var(--lg_bullet_icon_hover_bg, #698); cursor: pointer; } .litegraph textarea { resize: both; } - /* OLD */ .graphcontextmenu { padding: 4px; min-width: 100px; } - .graphcontextmenu-title { - color: #dde; - background-color: #222; + color: var(--lg_menu_old_title_fg, #dde); + background-color: var(--lg_menu_old_title_bg, #222); margin: 0; padding: 2px; cursor: default; @@ -541,7 +525,7 @@ .graphmenu-entry.event, .litemenu-entry.event { - border-left: 8px solid orange; + border-left: 8px solid var(--lg_menu_event_border, orange); padding-left: 12px; } @@ -550,16 +534,16 @@ } .graphmenu-entry.submenu { - border-right: 2px solid #eee; + border-right: 2px solid var(--lg_menu_submenu_border, #eee); } .graphmenu-entry:hover { - background-color: #555; + background-color: var(--lg_menu_hover_bg, #555); } .graphmenu-entry.separator { - background-color: #111; - border-bottom: 1px solid #666; + background-color: var(--lg_menu_separator_bg, #111); + border-bottom: 1px solid var(--lg_menu_separator_border, #666); height: 1px; width: calc(100% - 20px); -moz-width: calc(100% - 20px); @@ -576,7 +560,7 @@ .graphmenu-entry .property_value, .litemenu-entry .property_value { display: inline-block; - background-color: rgba(0, 0, 0, 0.5); + background-color: var(--lg_menu_property_value_bg, rgba(0, 0, 0, 0.5)); text-align: right; min-width: 80px; min-height: 1.2em; @@ -589,9 +573,9 @@ top: 10px; left: 10px; min-height: 2em; - background-color: #333; + background-color: var(--lg_dialog_old_bg, #333); font-size: 1.2em; - box-shadow: 0 0 10px black !important; + box-shadow: 0 0 10px var(--lg_dialog_old_shadow, black) !important; z-index: 10; } @@ -601,7 +585,7 @@ } .graphdialog .name { - color: #888; + color: var(--lg_dialog_name_fg, #888); display: inline-block; min-width: 60px; min-height: 1.5em; @@ -614,13 +598,12 @@ margin: 3px; min-width: 60px; min-height: 1.5em; - background-color: black; + background-color: var(--lg_dialog_input_bg, black); border: 0; - color: white; + color: var(--lg_dialog_input_fg, white); padding-left: 10px; outline: none; } - .graphdialog textarea { min-height: 150px; resize: both; @@ -629,7 +612,7 @@ .graphdialog button { margin-top: 3px; vertical-align: top; - background-color: #999; + background-color: var(--gd_button_bg, #999); border: 0; } @@ -650,8 +633,8 @@ .graphdialog .help-item:hover, .graphdialog .help-item.selected { cursor: pointer; - background-color: white; - color: black; + background-color: var(--gd_help_item_bg, white); + color: var(--gd_help_item_fg, black); } .litegraph .dialog { diff --git a/packages/core/package.json b/packages/core/package.json index 1ec5f9ac..bd0cabbf 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@litegraph-ts/core", - "version": "0.2.24", + "version": "0.2.25", "description": "A graph node editor similar to PD or UDK Blueprints. It works in an HTML5 Canvas and allows to export graphs to be included in applications.", "source": "src/index.ts", "types": "src/index.ts", diff --git a/packages/core/src/ContextMenu.ts b/packages/core/src/ContextMenu.ts index 815b7636..cf74c280 100644 --- a/packages/core/src/ContextMenu.ts +++ b/packages/core/src/ContextMenu.ts @@ -3,6 +3,14 @@ import LiteGraph from "./LiteGraph"; import type { MouseEventExt, CustomEventExt, EventExt } from "./DragAndScale"; import INodeSlot, { SlotInPosition } from "./INodeSlot"; import LGraphCanvas from "./LGraphCanvas"; +import { CssVars } from "./misc/CssVars"; + +export const contextmenu_vars = new CssVars({ + lg_combo_menu_selected_bg: "#ccc", + lg_combo_menu_selected_fg: "#000", + lg_menu_filter_bg: "transparent", + lg_menu_filter_fg: "#fff", +}); export interface ContextMenuRoot extends HTMLDivElement { closing_timer?: number; @@ -540,9 +548,8 @@ export default class ContextMenu { $filter.style.outline = "none"; $filter.style.border = "none"; $filter.style.boxSizing = "border-box"; - $filter.style.backgroundColor = - "var(--litegraph-menu-filter-bgcolor, transparent)"; - $filter.style.color = "var(--litegraph-menu-filter-color, white)"; + $filter.style.backgroundColor = contextmenu_vars.vars.lg_menu_filter_bg; + $filter.style.color = contextmenu_vars.vars.lg_menu_filter_fg; $root.prepend($filter); $filter.focus(); @@ -585,10 +592,14 @@ export default class ContextMenu { selectedItem?.style.setProperty( "background-color", - "#ccc", + contextmenu_vars.vars.lg_combo_menu_selected_bg, + "important", + ); + selectedItem?.style.setProperty( + "color", + contextmenu_vars.vars.lg_combo_menu_selected_fg, "important", ); - selectedItem?.style.setProperty("color", "#000", "important"); } const updateListPosition = () => { diff --git a/packages/core/src/LGraphCanvas.ts b/packages/core/src/LGraphCanvas.ts index 063c302c..1bfdf16b 100644 --- a/packages/core/src/LGraphCanvas.ts +++ b/packages/core/src/LGraphCanvas.ts @@ -206,6 +206,8 @@ export default class LGraphCanvas link_type_colors: Record = {}; + clear_background_color: string | null = null; + static gradients: object = {}; static search_limit: number; diff --git a/packages/core/src/LGraphCanvas_Rendering.ts b/packages/core/src/LGraphCanvas_Rendering.ts index f04beae7..d2778578 100644 --- a/packages/core/src/LGraphCanvas_Rendering.ts +++ b/packages/core/src/LGraphCanvas_Rendering.ts @@ -816,6 +816,20 @@ export default class LGraphCanvas_Rendering { this.ds.toCanvasContext(ctx); //render BG + if ( + this.ds.scale < 1.5 && + !bg_already_painted && + this.clear_background_color + ) { + ctx.fillStyle = this.clear_background_color; + ctx.fillRect( + this.visible_area[0], + this.visible_area[1], + this.visible_area[2], + this.visible_area[3], + ); + } + if ( this.background_image && this.ds.scale > 0.5 && @@ -1508,6 +1522,13 @@ export default class LGraphCanvas_Rendering { } ctx.fill(); + if (LiteGraph.NODE_OUTLINE_WIDTH) { + ctx.lineWidth = LiteGraph.NODE_OUTLINE_WIDTH; + ctx.strokeStyle = LiteGraph.WIDGET_OUTLINE_COLOR; + ctx.stroke(); + ctx.strokeStyle = fgColor; + } + //separator if (!node.flags.collapsed && render_title) { ctx.shadowColor = "transparent"; @@ -1646,6 +1667,26 @@ export default class LGraphCanvas_Rendering { Math.PI * 2, ); ctx.fill(); + + // 3 lines icon + // ctx.fillRect( + // 10, + // 5 - box_size - 1, + // box_size * 1.15, + // box_size * 0.15, + // ); + // ctx.fillRect( + // 10, + // 5 - box_size * 1.5 - 1, + // box_size * 1.15, + // box_size * 0.15, + // ); + // ctx.fillRect( + // 10, + // 5 - box_size * 2 - 1, + // box_size * 1.15, + // box_size * 0.15, + // ); } } else { if (low_quality) { diff --git a/packages/core/src/LGraphTheme.ts b/packages/core/src/LGraphTheme.ts new file mode 100644 index 00000000..0c9d6f0b --- /dev/null +++ b/packages/core/src/LGraphTheme.ts @@ -0,0 +1,254 @@ +import { contextmenu_vars } from "./ContextMenu"; +import LGraph from "./LGraph"; +import LGraphCanvas from "./LGraphCanvas"; +import type { NodeColor } from "./LGraphCanvas"; +import LiteGraph from "./LiteGraph"; +import { cloneDeep } from "./utils"; + +type ContextMenuVars = Record< + keyof (typeof contextmenu_vars)["config"], + string | number +>; +type UIVars = Record; + +const default_style_vars = { + lg_menu_fg: "#aaf", + lg_menu_bg_dark: "#000", + lg_menu_bar_fg: "#999", + lg_menu_bar_hover_bg: "#777", + lg_menu_bar_hover_fg: "#eee", + lg_menu_panel_bg: "#444", + lg_menu_panel_shadow: "black", + lg_menu_panel_border: "#aaf", + lg_menu_entry_fg: "#aaa", + lg_menu_entry_checked_bg: "#aaf", + lg_menu_entry_separator_top: "#333", + lg_menu_entry_separator_bottom: "#666", + lg_menu_entry_submenu_border: "cyan", + lg_menu_title_fg: "#dde", + lg_menu_title_bg: "#111", + lg_menu_entry_hover_bg: "#444", + lg_menu_entry_hover_fg: "#eee", + lg_menu_entry_property_value_bg: "rgba(0, 0, 0, 0.5", + lg_searchbox_bg: "rgba(0, 0, 0, 0.5", + lg_searchbox_input_bg: "black", + lg_searchbox_fg: "white", + lg_searchitem_bg: "rgba(0, 0, 0, 0.5", + lg_searchitem_fg: "white", + lg_searchitem_not_in_filter_fg: "#b99", + lg_searchitem_generic_type_fg: "#999", + lg_searchitem_hover_bg: "white", + lg_searchitem_hover_fg: "black", + lg_dialog_bg: "#2a2a2a", + lg_dialog_shadow: "#111", + lg_dialog_close_hover: "white", + lg_dialog_header_fg: "#aaa", + lg_dialog_header_border: "#161616", + lg_dialog_footer_border: "#1a1a1a", + lg_dialog_fg: "#aaa", + lg_dialog_side_bg: "black", + lg_separator_top: "#000", + lg_separator_bottom: "#333", + lg_property_hover: "#545454", + lg_property_name_fg: "#737373", + lg_dialog_hover_fg: "white", + lg_dialog_value_fg: "#aaa", + lg_dialog_value_bg: "#1a1a1a", + lg_dialog_value_hover_fg: "white", + lg_dialog_bool_fg: "#a88", + lg_dialog_bool_on_fg: "#8a8", + lg_dialog_btn_bg: "#060606", + lg_dialog_btn_fg: "#8e8e8e", + lg_dialog_btn_hover_bg: "#111", + lg_dialog_btn_hover_fg: "#fff", + lg_dialog_btn_delete_hover_bg: "#f33", + lg_dialog_btn_delete_hover_fg: "black", + lg_subgraph_hover_bg: "#333", + lg_subgraph_type_bg: "black", + lg_subgraph_input_fg: "#ccc", + lg_subgraph_input_bg: "#1a1a1a", + lg_subgraph_property_bg: "#1c1c1c", + lg_subgraph_property_fg: "#aaa", + lg_subgraph_property_extra_fg: "#ccc", + lg_subgraph_property_extra_bg: "#111", + lg_bullet_icon_bg: "#666", + lg_bullet_icon_hover_bg: "#698", + lg_menu_old_title_fg: "#dde", + lg_menu_old_title_bg: "#222", + lg_menu_event_border: "orange", + lg_menu_submenu_border: "#eee", + lg_menu_hover_bg: "#555", + lg_menu_separator_bg: "#111", + lg_menu_separator_border: "#666", + lg_menu_property_value_bg: "rgba(0, 0, 0, 0.5", + lg_dialog_old_bg: "#333", + lg_dialog_old_shadow: "black", + lg_dialog_name_fg: "#888", + lg_dialog_input_bg: "black", + lg_dialog_input_fg: "white", + gd_button_bg: "#999", + gd_help_item_bg: "white", + gd_help_item_fg: "black", +} as const; + +type DeepPartial = + T extends Record + ? { [K in keyof T]?: DeepPartial } + : T; + +// prettier-ignore +export type ILGraphTheme = { + graph: { + CANVAS_GRID_SIZE: number; + NODE_TITLE_HEIGHT: number; + NODE_TITLE_TEXT_Y: number; + NODE_SLOT_HEIGHT: number; + NODE_WIDGET_HEIGHT: number; + NODE_WIDTH: number; + NODE_MIN_WIDTH: number; + NODE_COLLAPSED_RADIUS: number; + NODE_COLLAPSED_WIDTH: number; + NODE_TITLE_COLOR: string; + NODE_SELECTED_TITLE_COLOR: string; + NODE_TEXT_SIZE: number; + NODE_TEXT_COLOR: string; + NODE_SUBTEXT_SIZE: number; + NODE_DEFAULT_COLOR: string; + NODE_DEFAULT_BGCOLOR: string; + NODE_DEFAULT_BOXCOLOR: string; + NODE_DEFAULT_SHAPE: string; + NODE_BOX_OUTLINE_COLOR: string; + DEFAULT_SHADOW_COLOR: string; + DEFAULT_GROUP_FONT_SIZE: number; + WIDGET_BGCOLOR: string; + WIDGET_OUTLINE_COLOR: string; + WIDGET_TEXT_COLOR: string; + WIDGET_SECONDARY_TEXT_COLOR: string; + EVENT_LINK_COLOR: string; + CONNECTING_LINK_COLOR: string; + NODE_OUTLINE_WIDTH: number; + NODE_OUTLINE_COLOR: string; + }; + canvas: { + node_colors: Record; + type_colors: Record; + + node_title_color: string; + title_text_font: string; + inner_text_font: string; + default_link_color: string; + + background_image: string; + clear_background_color: string; + }; + ui: UIVars & + ContextMenuVars; +} + +export class LGraphTheme { + // prettier-ignore + static defaultTheme = new LGraphTheme({ + graph: { + CANVAS_GRID_SIZE: LiteGraph.CANVAS_GRID_SIZE, + NODE_TITLE_HEIGHT: LiteGraph.NODE_TITLE_HEIGHT, + NODE_TITLE_TEXT_Y: LiteGraph.NODE_TITLE_TEXT_Y, + NODE_SLOT_HEIGHT: LiteGraph.NODE_SLOT_HEIGHT, + NODE_WIDGET_HEIGHT: LiteGraph.NODE_WIDGET_HEIGHT, + NODE_WIDTH: LiteGraph.NODE_WIDTH, + NODE_MIN_WIDTH: LiteGraph.NODE_MIN_WIDTH, + NODE_COLLAPSED_RADIUS: LiteGraph.NODE_COLLAPSED_RADIUS, + NODE_COLLAPSED_WIDTH: LiteGraph.NODE_COLLAPSED_WIDTH, + NODE_SELECTED_TITLE_COLOR: LiteGraph.NODE_SELECTED_TITLE_COLOR, + NODE_TEXT_COLOR: LiteGraph.NODE_TEXT_COLOR, + NODE_SUBTEXT_SIZE: LiteGraph.NODE_SUBTEXT_SIZE, + NODE_DEFAULT_COLOR: LiteGraph.NODE_DEFAULT_COLOR, + NODE_DEFAULT_BGCOLOR: LiteGraph.NODE_DEFAULT_BGCOLOR, + NODE_DEFAULT_BOXCOLOR: LiteGraph.NODE_DEFAULT_BOXCOLOR, + NODE_DEFAULT_SHAPE: LiteGraph.NODE_DEFAULT_SHAPE, + NODE_BOX_OUTLINE_COLOR: LiteGraph.NODE_BOX_OUTLINE_COLOR, + DEFAULT_SHADOW_COLOR: LiteGraph.DEFAULT_SHADOW_COLOR, + DEFAULT_GROUP_FONT_SIZE: LiteGraph.DEFAULT_GROUP_FONT_SIZE, + WIDGET_BGCOLOR: LiteGraph.WIDGET_BGCOLOR, + WIDGET_OUTLINE_COLOR: LiteGraph.WIDGET_OUTLINE_COLOR, + WIDGET_TEXT_COLOR: LiteGraph.WIDGET_TEXT_COLOR, + WIDGET_SECONDARY_TEXT_COLOR: LiteGraph.WIDGET_SECONDARY_TEXT_COLOR, + EVENT_LINK_COLOR: LiteGraph.EVENT_LINK_COLOR, + CONNECTING_LINK_COLOR: LiteGraph.CONNECTING_LINK_COLOR, + NODE_OUTLINE_WIDTH: LiteGraph.NODE_OUTLINE_WIDTH, + NODE_OUTLINE_COLOR: LiteGraph.NODE_OUTLINE_COLOR, + }, + canvas: { + node_colors: LGraphCanvas.node_colors, + type_colors: LGraphCanvas.DEFAULT_LINK_TYPE_COLORS, + + node_title_color: LiteGraph.NODE_TITLE_COLOR, + title_text_font: "" + LiteGraph.NODE_TEXT_SIZE + "px Arial", + inner_text_font: "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial", + default_link_color: LiteGraph.LINK_COLOR, + + background_image: LGraphCanvas.DEFAULT_BACKGROUND_IMAGE, + clear_background_color: null, + }, + ui: { + ...default_style_vars, + ...contextmenu_vars.config + } + }); + + readonly theme: DeepPartial; + + constructor(theme: DeepPartial) { + this.theme = cloneDeep(theme); + } + + apply(context: { graph: LGraph; canvas: LGraphCanvas }) { + const { graph, canvas } = context; + const { theme } = this; + + // apply graph theme colors + Object.entries(theme.graph || {}).forEach(([k, v]) => { + LiteGraph[k] = v; + }); + + // apply canvas theme colors + const { + node_colors, + type_colors, + node_title_color, + title_text_font, + inner_text_font, + default_link_color, + background_image, + clear_background_color, + } = this.theme.canvas || {}; + if (node_colors) { + Object.assign(LGraphCanvas.node_colors, node_colors); + } + if (type_colors) { + Object.assign( + LGraphCanvas.DEFAULT_CONNECTION_COLORS_BY_TYPE, + type_colors, + ); + Object.assign(LGraphCanvas.DEFAULT_LINK_TYPE_COLORS, type_colors); + Object.assign(canvas.link_type_colors, type_colors); + } + if (node_title_color) canvas.node_title_color = node_title_color; + if (title_text_font) canvas.title_text_font = title_text_font; + if (inner_text_font) canvas.inner_text_font = inner_text_font; + if (default_link_color) canvas.default_link_color = default_link_color; + if (background_image) { + canvas.background_image = background_image; + canvas._pattern = null; + canvas._pattern_img = null; + } + if (clear_background_color) + canvas.clear_background_color = clear_background_color; + if (theme.canvas) canvas.draw(true, true); + + // apply ui css property + const rootStyle = document.documentElement.style; + Object.entries(theme.ui || {}).forEach(([k, v]) => { + rootStyle.setProperty("--" + k, String(v)); + }); + } +} diff --git a/packages/core/src/LiteGraph.ts b/packages/core/src/LiteGraph.ts index 8c76836e..55e12209 100644 --- a/packages/core/src/LiteGraph.ts +++ b/packages/core/src/LiteGraph.ts @@ -63,6 +63,9 @@ export default class LiteGraph { static MAX_NUMBER_OF_NODES: number = 1000; //avoid infinite loops static DEFAULT_POSITION: Vector2 = [100, 100]; //default node position + static NODE_OUTLINE_WIDTH: number = 0; + static NODE_OUTLINE_COLOR: string = "#666"; + static proxy: any = null; //used to redirect calls static node_images_path: string = ""; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 65b880b5..8703aebe 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -69,3 +69,6 @@ export type * from "./nodes/Subgraph"; export * from "./widgets/DOMWidget"; export type * from "./widgets/DOMWidget"; + +export * from "./LGraphTheme"; +export type * from "./LGraphTheme"; diff --git a/packages/core/src/misc/CssVars.ts b/packages/core/src/misc/CssVars.ts new file mode 100644 index 00000000..04604ff4 --- /dev/null +++ b/packages/core/src/misc/CssVars.ts @@ -0,0 +1,20 @@ +export class CssVars> { + constructor(readonly config: T) {} + + var(key: keyof T, def: string | number = this.config[key]) { + return `var(--${String(key)}, ${def})`; + } + + get vars() { + const root = {}; + return new Proxy(root, { + get: (target, p, receiver) => { + if (p in root) return Reflect.get(target, p, receiver); + if (typeof p === "string") { + return this.var(p); + } + return Reflect.get(target, p, receiver); + }, + }) as Record; + } +} diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index cc5296b3..2f82d2c7 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -96,3 +96,10 @@ export function isValidLitegraphType(type: any): type is SlotType { typeof type === "string" ); } + +export function cloneDeep(o: any): T { + const impl = + globalThis.structuredClone ?? + ((x: any) => JSON.parse(JSON.stringify(x))); + return impl(o); +}