diff --git a/demos/windows.html b/demos/windows.html
new file mode 100644
index 00000000..5694ca90
--- /dev/null
+++ b/demos/windows.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ Windows - SGNUIKit Demo
+
+
+
+
+
+
+
+
+
+
diff --git a/src/css/components/components.css b/src/css/components/components.css
index 45daf1a9..d8594e7a 100644
--- a/src/css/components/components.css
+++ b/src/css/components/components.css
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 SGNetworks. All rights reserved.
+ * Copyright (c) 2022-2023 SGNetworks. All rights reserved.
*
* The software is an exclusive copyright of "SGNetworks" and is provided as is exclusively with only "USAGE" access. "Modification", "Alteration", "Re-distribution" is completely prohibited.
* VIOLATING THE ABOVE TERMS IS A PUNISHABLE OFFENSE WHICH MAY LEAD TO LEGAL CONSEQUENCES.
@@ -22,4 +22,5 @@
@import "tablayout.css";
@import "views.css";
@import "wizard.css";
+@import "windows.css";
diff --git a/src/css/components/windows.css b/src/css/components/windows.css
new file mode 100644
index 00000000..ad06fb97
--- /dev/null
+++ b/src/css/components/windows.css
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2022-2023 SGNetworks. All rights reserved.
+ *
+ * The software is an exclusive copyright of "SGNetworks" and is provided as is exclusively with only "USAGE" access. "Modification", "Alteration", "Re-distribution" is completely prohibited.
+ * VIOLATING THE ABOVE TERMS IS A PUNISHABLE OFFENSE WHICH MAY LEAD TO LEGAL CONSEQUENCES.
+ */
+
+.sgn-window-group {
+ align-items: center;
+ background-color: var(--sgn-semi-transparent-light);
+ display: flex;
+ height: 100%;
+ justify-content: center;
+ left: 0;
+ opacity: 0;
+ position: absolute;
+ top: 0;
+ transition: opacity 0.5s ease-in-out;
+ width: 100%;
+ z-index: 9999;
+}
+
+.sgn-window-dock {
+ align-items: center;
+ background-color: var(--sgn-primary-lightest);
+ border-top: 1px solid var(--sgn-border-light);
+ bottom: 0;
+ display: flex;
+ height: 3rem;
+ justify-content: center;
+ left: 0;
+ position: fixed;
+ width: 100%;
+ z-index: 1;
+}
+
+.sgn-window {
+ background-color: var(--sgn-accent-background);
+ border-radius: var(--sgn-border-radius-md);
+ box-shadow: 0 0 .5rem .1rem var(--sgn-semi-transparent-light);
+ display: flex;
+ flex-direction: column;
+ max-width: fit-content;
+ min-width: 25%;
+ overflow: hidden;
+ transition: transform 0.5s ease-in-out;
+ z-index: 2;
+}
+
+.sgn-window > .title-bar {
+ align-items: center;
+ background-color: var(--sgn-accent-primary);
+ border-bottom: 1px solid var(--sgn-border-light);
+ color: var(--sgn-accent-primary-text);
+ display: flex;
+ font-family: var(--sgn-font-default-bold);
+ justify-content: space-between;
+ padding: var(--padding-md) var(--padding-md);
+ position: relative;
+}
+
+.sgn-window > .title-bar > .window-actions {
+ display: flex;
+ justify-content: end;
+}
+
+.sgn-window > .title-bar > .window-actions > .sgn-window-action-btn {
+ align-items: center;
+ background: none;
+ border: var(--sgn-button-border-width) solid;
+ border-color: var(--sgn-accent-primary-text);
+ border-radius: var(--sgn-border-radius-sm);
+ color: var(--sgn-accent-primary-text);
+ cursor: pointer;
+ display: inline-flex;
+ height: 26px;
+ justify-content: center;
+ margin: 0 var(--margin-xs);
+ padding: .5rem .65rem;
+ text-align: center;
+ text-decoration: none;
+ text-transform: uppercase;
+ transition: background-color, color .3s linear;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ user-select: none;
+ width: 26px;
+}
+
+.sgn-window > .title-bar > .window-actions > .sgn-window-action-btn:first-child {
+ margin-left: 0;
+}
+
+.sgn-window > .title-bar > .window-actions > .sgn-window-action-btn:last-child {
+ margin-right: 0;
+}
+
+.sgn-window > .title-bar > .window-actions > .sgn-window-action-btn > .sgn-window-action-icon {
+ align-items: center;
+ display: inline-flex;
+ font-style: normal;
+ font-weight: 900;
+ justify-content: center;
+}
+
+.sgn-window > .title-bar > .window-actions > .sgn-window-closeBtn > .sgn-window-action-icon:before {
+ content: "\f00d";
+}
+
+.sgn-window > .title-bar > .window-actions > .sgn-window-maximizeBtn > .sgn-window-action-icon:before {
+ content: "\f065";
+}
+
+.sgn-window > .title-bar > .window-actions > .sgn-window-fullscreenBtn > .sgn-window-action-icon:before {
+ content: "\f31e";
+}
+
+.sgn-window > .title-bar > .window-actions > .sgn-window-compressBtn > .sgn-window-action-icon:before {
+ content: "\f066";
+}
+
+.sgn-window > .title-bar > .window-actions > .sgn-window-minimizeBtn > .sgn-window-action-icon:before {
+ content: "\f2d1";
+}
+
+.sgn-window > .title-bar > .window-actions > .sgn-window-action-btn:hover {
+ background-color: var(--sgn-accent-primary-text);
+ border-color: var(--sgn-accent-primary-text);
+ color: var(--sgn-secondary-light);
+}
+
+.sgn-window > .title-bar > .window-actions > .sgn-window-closeBtn {
+ border-color: var(--sgn-danger-light);
+ color: var(--sgn-danger-light);
+}
+
+.sgn-window > .title-bar > .window-actions > .sgn-window-closeBtn:hover {
+ background-color: var(--sgn-danger);
+ border-color: var(--sgn-danger);
+ color: var(--sgn-accent-primary-text);
+}
+
+.sgn-window > .window-body {
+ background-color: var(--sgn-accent-hud);
+ min-height: 10rem;
+ overflow: auto;
+ padding: var(--padding-md);
+}
+
+.sgn-window > .window-footer {
+ border-top: 1px solid var(--sgn-border-light);
+ display: flex;
+ justify-content: end;
+ padding: var(--padding-sm) var(--padding-md);
+}
+
+.sgn-window > .window-footer > .btn {
+ line-height: 1;
+ margin: 0 var(--margin-xs);
+ padding: var(--padding-sm);
+}
+
+
+.sgn-window.sgn-window-draggable,
+.sgn-window.sgn-window-resizable {
+ position: absolute;
+ top: 80px;
+}
+
+.sgn-window.minimized {
+ bottom: unset !important;
+ left: unset !important;
+ right: unset !important;
+ top: unset !important;
+}
+
+.sgn-window.minimized > .sgn-window-resizer-handle {
+ pointer-events: none;
+}
+
+.sgn-window.minimized > .title-bar {
+ cursor: pointer;
+ padding: var(--padding-sm);
+}
+
+.sgn-window.maximized {
+ bottom: 0 !important;
+ height: 100% !important;
+ left: 0 !important;
+ right: 0 !important;
+ top: 0 !important;
+ width: 100% !important;
+}
+
+.sgn-window.maximized > .window-body,
+.sgn-window.fullscreen > .window-body {
+ height: 100% !important;
+}
+
+.sgn-window.maximized > .sgn-window-resizer-handle,
+.sgn-window.fullscreen > .sgn-window-resizer-handle {
+ pointer-events: none !important;
+}
+
+.sgn-window.maximized > .title-bar > .window-actions > .sgn-window-maximizeBtn > .sgn-window-action-icon:before {
+ content: "\f2d2";
+}
+
+.sgn-window.fullscreen > .title-bar > .window-actions > .sgn-window-fullscreenBtn > .sgn-window-action-icon:before {
+ content: "\f066";
+}
+
+/*** RESIZABLE WINDOWS ***/
+.sgn-window.sgn-window-resizable {
+ max-width: unset;
+ min-width: unset;
+}
+
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle {
+ background-color: transparent;
+ position: absolute;
+}
+
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle.handle-left,
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle.handle-right {
+ cursor: e-resize;
+ height: 100%;
+ top: 0;
+ width: 20px;
+}
+
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle.handle-top,
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle.handle-bottom {
+ cursor: n-resize;
+ height: 20px;
+ left: 0;
+ width: 100%;
+}
+
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle.handle-top_left,
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle.handle-top_right,
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle.handle-bottom_left,
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle.handle-bottom_right {
+ height: 20px;
+ width: 20px;
+}
+
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle.handle-top_left {
+ cursor: nw-resize;
+}
+
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle.handle-top_right {
+ cursor: ne-resize;
+}
+
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle.handle-bottom_left {
+ cursor: sw-resize;
+}
+
+.sgn-window.sgn-window-resizable > .sgn-window-resizer-handle.handle-bottom_right {
+ cursor: se-resize;
+}
+
+.sgn-window.sgn-window-resizable.resized-y > .window-body {
+ height: 100%;
+ min-height: unset;
+}
+
+
+/*** SIZES ***/
+.sgn-window.window-xxxs {
+ max-width: 10%
+}
+
+.sgn-window.window-xxs {
+ max-width: 20%
+}
+
+.sgn-window.window-xs {
+ max-width: 30%;
+}
+
+.sgn-window.window-sm {
+ max-width: 35%
+}
+
+.sgn-window.window-md {
+ max-width: 40%
+}
+
+.sgn-window.window-hw {
+ max-width: 50%;
+}
+
+.sgn-window.window-lg {
+ max-width: 60%;
+}
+
+.sgn-window.window-xl {
+ max-width: 70%;
+}
+
+.sgn-window.window-xxl {
+ max-width: 80%;
+}
+
+.sgn-window.window-xxxl {
+ max-width: 90%;
+}
+
+.sgn-window.window-fw {
+ max-width: 100%;
+}
+
+
+@supports ((-webkit-backdrop-filter: none) or (backdrop-filter: none)) {
+ .sgn-window-group {
+ backdrop-filter: blur(5px);
+ }
+}
diff --git a/src/js/components/components.js b/src/js/components/components.js
index e45264b0..8413c597 100644
--- a/src/js/components/components.js
+++ b/src/js/components/components.js
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 SGNetworks. All rights reserved.
+ * Copyright (c) 2022-2023 SGNetworks. All rights reserved.
*
* The software is an exclusive copyright of "SGNetworks" and is provided as is exclusively with only "USAGE" access. "Modification", "Alteration", "Re-distribution" is completely prohibited.
* VIOLATING THE ABOVE TERMS IS A PUNISHABLE OFFENSE WHICH MAY LEAD TO LEGAL CONSEQUENCES.
@@ -11,5 +11,6 @@ import('./marquee.js');
import('./modals.js');
import('./sidebar.js');
import('./wizard.js');
-import('./tablayout.js');
+import("./tablayout.js");
+import("./windows.js");
diff --git a/src/js/components/windows.js b/src/js/components/windows.js
new file mode 100644
index 00000000..3d592d15
--- /dev/null
+++ b/src/js/components/windows.js
@@ -0,0 +1,959 @@
+/*
+ * Copyright (c) 2022-2023 SGNetworks. All rights reserved.
+ *
+ * The software is an exclusive copyright of "SGNetworks" and is provided as is exclusively with only "USAGE" access. "Modification", "Alteration", "Re-distribution" is completely prohibited.
+ * VIOLATING THE ABOVE TERMS IS A PUNISHABLE OFFENSE WHICH MAY LEAD TO LEGAL CONSEQUENCES.
+ */
+
+"use strict"; // Start of use strict
+if(typeof jQuery === "undefined") {
+ throw new Error("SGNModals requires jQuery");
+}
+
+(function(window, document, $) {
+ "use strict";
+ const metaTag = document.createElement("meta");
+ metaTag.name = "viewport";
+ metaTag.content = "user-scalable=0";
+ document.getElementsByTagName("head")[0].appendChild(metaTag);
+
+ const SGNWindow = function($elem) {
+ const plugin = this;
+ let $this = $elem;
+ let $windowDock = $(".sgn-window-dock"),
+ $windowTitleBar = $this.children(".title-bar"),
+ $windowBody = $this.children(".window-body"),
+ $windowFooter = $this.children(".window-footer");
+ let minW = 0, minH = 0, currentWindowWidth = 0, currentWindowHeight = 0, $container, guid;
+
+ const GUID = str => {
+ if(str === undefined || !str.length)
+ str = "" + Math.random() * new Date().getTime() + Math.random();
+
+ let c = 0,
+ r = "";
+
+ for(let i = 0; i < str.length; i++)
+ c = (c + (str.charCodeAt(i) * (i + 1) - 1)) & 0xfffffffffffff;
+
+ str = str.substr(str.length / 2) + c.toString(16) + str.substr(0, str.length / 2);
+ for(let i = 0, p = c + str.length; i < 32; i++) {
+ if(i === 8 || i === 12 || i === 16 || i === 20)
+ r += "-";
+
+ c = p = (str[(i ** i + p + 1) % str.length]).charCodeAt(0) + p + i;
+ if(i === 12)
+ c = (c % 5) + 1; //1-5
+ else if(i === 16)
+ c = (c % 4) + 8; //8-B
+ else
+ c %= 16; //0-F
+
+ r += c.toString(16);
+ }
+ return r;
+ };
+
+ /***
+ *
+ * @param {jQuery} $elem
+ * @param {('left'|'top'|'right'|'bottom','x'|'y')} which
+ * @param {boolean} combined
+ * @returns {number}
+ */
+ const getPadding = ($elem, which, combined = false) => {
+ const pl = parseFloat($elem.css("padding-left")),
+ pr = parseFloat($elem.css("padding-right")),
+ pt = parseFloat($elem.css("padding-top")),
+ pb = parseFloat($elem.css("padding-bottom")),
+ p = parseFloat($elem.css("padding"));
+ const px = (combined) ? (pl + pr) : pl | pr,
+ py = (combined) ? (pt + pb) : pt | pb;
+
+ if(which === "left")
+ return pl;
+ else if(which === "right")
+ return pr;
+ else if(which === "top")
+ return pt;
+ else if(which === "bottom")
+ return pb;
+ else if(which === "x" || which === "horizontal")
+ return px | (p / 2);
+ else if(which === "y" || which === "vertical")
+ return py | (p / 2);
+ else
+ return p;
+ };
+
+ /***
+ *
+ * @param {jQuery} $elem
+ * @param {('left'|'top'|'right'|'bottom','x'|'y')} which
+ * @param {boolean} combined
+ * @returns {number}
+ */
+ const getMargin = ($elem, which, combined = false) => {
+ const ml = parseFloat($elem.css("margin-left")),
+ mr = parseFloat($elem.css("margin-right")),
+ mt = parseFloat($elem.css("margin-top")),
+ mb = parseFloat($elem.css("margin-bottom")),
+ m = parseFloat($elem.css("margin"));
+ const mx = (combined) ? (ml + mr) : ml | mr,
+ my = (combined) ? (mt + mb) : mt | mb;
+
+ if(which === "left")
+ return ml;
+ else if(which === "right")
+ return mr;
+ else if(which === "top")
+ return mt;
+ else if(which === "bottom")
+ return mb;
+ else if(which === "x" || which === "horizontal")
+ return mx | (m / 2);
+ else if(which === "y" || which === "vertical")
+ return my | (m / 2);
+ else
+ return m;
+ };
+ const getCombinedPadding = ($elem) => ($elem.innerWidth() - $elem.width());
+ const getCombinedMargin = ($elem) => ($elem.outerWidth(true) - $elem.outerWidth());
+ const getCombinedBorder = ($elem) => ($elem.outerWidth() - $elem.innerWidth());
+ const getOffset = ($elem) => (getCombinedPadding($elem) + getCombinedMargin($elem) + getCombinedBorder($elem));
+ const getWidth = ($elem) => ($elem.width() + getOffset($elem));
+ const getHeight = ($elem) => ($elem.height() + getOffset($elem));
+
+ const init = () => {
+ let $windowTitle = $windowTitleBar.children(".window-title"),
+ $windowActions = $windowTitleBar.children(".window-actions");
+ let $closeBtn = $windowActions.children(".sgn-window-closeBtn"),
+ $fullscreenBtn = $windowActions.children(".sgn-window-fullscreenBtn"),
+ $maximizeBtn = $windowActions.children(".sgn-window-maximizeBtn"),
+ $minimizeBtn = $windowActions.children(".sgn-window-minimizeBtn");
+ const windowTitle = $this.attr("title") || "Window Title";
+
+ const closeBtnHTML = ``,
+ fullscreenBtnHTML = ``,
+ maximizeBtnHTML = ``,
+ minimizeBtnHTML = ``;
+ const windowDockHTML = ``,
+ windowTitleHTML = `
+
+
+ Open Window
+
+
+
+
+
+
+ Some quick example text to build on the card title and make up the bulk of the card's content.
+
+
+ ${windowTitle}
`,
+ windowActionsHTML = `${minimizeBtnHTML}${maximizeBtnHTML}${fullscreenBtnHTML}${closeBtnHTML}
`,
+ windowTitleBarHTML = ` `;
+ guid = "sgn-window-" + GUID();
+
+ if($windowDock.length < 1) {
+ const $body = $("body");
+ $body.append(windowDockHTML);
+ $windowDock = $body.children(".sgn-window-dock");
+ }
+ $windowDock.hide();
+
+ if($windowTitleBar.length === 1) {
+ if($windowActions.length === 1) {
+ if($minimizeBtn.length < 1) {
+ $windowActions.prepend(minimizeBtnHTML);
+ $minimizeBtn = $windowActions.children(".sgn-window-minimizeBtn");
+ }
+ if($maximizeBtn.length < 1) {
+ if($minimizeBtn.length === 1) {
+ $minimizeBtn.after(maximizeBtnHTML);
+ $maximizeBtn = $windowActions.children(".sgn-window-maximizeBtn");
+ }
+ }
+ if($fullscreenBtn.length < 1) {
+ if($maximizeBtn.length === 1) {
+ $maximizeBtn.after(fullscreenBtnHTML);
+ $fullscreenBtn = $windowActions.children(".sgn-window-fullscreenBtn");
+ }
+ }
+ if($closeBtn.length < 1) {
+ $windowActions.append(closeBtnHTML);
+ $closeBtn = $windowActions.children(".sgn-window-closeBtn");
+ }
+ } else {
+ $windowTitleBar.append(windowActionsHTML);
+ $windowActions = $windowTitleBar.children(".window-actions");
+ $minimizeBtn = $windowActions.children(".sgn-window-minimizeBtn");
+ $maximizeBtn = $windowActions.children(".sgn-window-maximizeBtn");
+ $fullscreenBtn = $windowActions.children(".sgn-window-fullscreenBtn");
+ $closeBtn = $windowActions.children(".sgn-window-closeBtn");
+ }
+ if($windowTitle.length < 1) {
+ $windowTitleBar.prepend(windowTitleHTML);
+ $windowTitle = $windowTitleBar.children(".window-title");
+ }
+ } else {
+ $this.prepend(windowTitleBarHTML);
+ $windowTitleBar = $this.children(".title-bar");
+ $windowActions = $windowTitleBar.children(".window-actions");
+ $minimizeBtn = $windowActions.children(".sgn-window-minimizeBtn");
+ $maximizeBtn = $windowActions.children(".sgn-window-maximizeBtn");
+ $fullscreenBtn = $windowActions.children(".sgn-window-fullscreenBtn");
+ $closeBtn = $windowActions.children(".sgn-window-closeBtn");
+ }
+
+ $this.addClass("sgn-window-draggable sgn-window-resizable");
+
+ const windowTitleBarPadding = getCombinedPadding($windowTitleBar),
+ windowTitleBarOffset = getOffset($windowTitleBar),
+ windowTitleBarHeight = getHeight($windowTitleBar),
+ windowTitleWidth = getWidth($windowTitle),
+ windowActionsWidth = getWidth($windowActions);
+
+ minW = (windowTitleWidth + windowActionsWidth + windowTitleBarOffset + getPadding($windowTitleBar, "x"));
+ minH = windowTitleBarHeight; //60px
+
+ if(empty($this.attr("id")))
+ $this.attr("id", guid);
+ guid = $this.attr("id");
+
+ initControls($minimizeBtn, $maximizeBtn, $fullscreenBtn, $closeBtn);
+ makeDraggable();
+ makeResizable(minW, minH);
+
+ $this.data("SGNWindow", plugin);
+ };
+
+ /***
+ *
+ * @param {jQuery} $minimizeBtn
+ * @param {jQuery} $maximizeBtn
+ * @param {jQuery} $fullscreenBtn
+ * @param {jQuery} $closeBtn
+ */
+ const initControls = ($minimizeBtn, $maximizeBtn, $fullscreenBtn, $closeBtn) => {
+ $windowTitleBar.on("click", (e) => {
+ e.preventDefault();
+
+ if($this.hasClass("minimized")) {
+ plugin.restore();
+ }
+ });
+
+ $minimizeBtn.on("click", (e) => {
+ e.preventDefault();
+
+ if(!$this.hasClass("minimized")) {
+ plugin.minimize();
+ }
+ });
+
+ $maximizeBtn.on("click", (e) => {
+ e.preventDefault();
+
+ if(!$this.hasClass("maximized")) {
+ plugin.maximize();
+ } else {
+ plugin.compress();
+ }
+ });
+
+ $fullscreenBtn.on("click", (e) => {
+ e.preventDefault();
+
+ plugin.toggleFullscreen();
+ });
+
+ $closeBtn.on("click", (e) => {
+ e.preventDefault();
+ plugin.close();
+ });
+
+ $windowTitleBar.off("click");
+ };
+
+ const makeResizable = (minW = 100, minH = 100, size = 20) => {
+ const element = $this[0];
+ const step = -(size / 2),
+ maxW = $(window).width(),
+ maxH = $(window).height();
+ const r_css = {
+ "backgroundColor": "transparent",
+ "position": "absolute",
+ },
+ cr_css = {
+ "width": `${size}px`,
+ "height": `${size}px`,
+ "backgroundColor": "transparent",
+ "position": "absolute",
+ },
+ er_css = $.extend({}, r_css, {"width": `${size}px`, "height": "100%", "cursor": "e-resize", "top": "0"}),
+ nr_css = $.extend({}, r_css, {"width": "100%", "height": `${size}px`, "cursor": "n-resize", "left": "0"}),
+ nwr_css = $.extend({}, cr_css, {"cursor": "nw-resize"}),
+ ner_css = $.extend({}, cr_css, {"cursor": "ne-resize"}),
+ swr_css = $.extend({}, cr_css, {"cursor": "sw-resize"}),
+ ser_css = $.extend({}, cr_css, {"cursor": "se-resize"});
+
+ const $left = $("").addClass("sgn-window-resizer-handle handle-left").css(er_css).css("left", `${step}px`),
+ $right = $("").addClass("sgn-window-resizer-handle handle-right").css(er_css).css("right", `${step}px`),
+ $top = $("").addClass("sgn-window-resizer-handle handle-top").css(nr_css).css("top", `${step}px`),
+ $bottom = $("").addClass("sgn-window-resizer-handle handle-bottom").css(nr_css).css("bottom", `${step}px`),
+ $cornerNW = $("").addClass("sgn-window-resizer-handle handle-top_left").css(nwr_css).css({"top": `${step}px`, "left": `${step}px`}),
+ $cornerNE = $("").addClass("sgn-window-resizer-handle handle-top_right").css(ner_css).css({"top": `${step}px`, "right": `${step}px`}),
+ $cornerSW = $("").addClass("sgn-window-resizer-handle handle-bottom_left").css(swr_css).css({"bottom": `${step}px`, "left": `${step}px`}),
+ $cornerSE = $("").addClass("sgn-window-resizer-handle handle-bottom_right").css(ser_css).css({"bottom": `${step}px`, "right": `${step}px`});
+
+ $left.on("mousedown", resizeXNegative());
+ $right.on("mousedown", resizeXPositive());
+ $top.on("mousedown", resizeYNegative());
+ $bottom.on("mousedown", resizeYPositive());
+ $cornerNW.on("mousedown", resizeXNegative());
+ $cornerNW.on("mousedown", resizeYNegative());
+ $cornerNE.on("mousedown", resizeXPositive());
+ $cornerNE.on("mousedown", resizeYNegative());
+ $cornerSW.on("mousedown", resizeXNegative());
+ $cornerSW.on("mousedown", resizeYPositive());
+ $cornerSE.on("mousedown", resizeXPositive());
+ $cornerSE.on("mousedown", resizeYPositive());
+
+ $this.append($left);
+ $this.append($right);
+ $this.append($top);
+ $this.append($bottom);
+ $this.append($cornerNW);
+ $this.append($cornerNE);
+ $this.append($cornerSW);
+ $this.append($cornerSE);
+
+ /*const top = document.createElement("div");
+ top.style.width = "100%";
+ top.style.height = size + "px";
+ top.style.backgroundColor = "transparent";
+ top.style.position = "absolute";
+ top.style.top = -(size / 2) + "px";
+ top.style.left = "0px";
+ top.style.cursor = "n-resize";
+
+ top.addEventListener("mousedown", resizeYNegative());
+
+ element.appendChild(top);
+
+ const bottom = document.createElement("div");
+ bottom.style.width = "100%";
+ bottom.style.height = size + "px";
+ bottom.style.backgroundColor = "transparent";
+ bottom.style.position = "absolute";
+ bottom.style.bottom = -(size / 2) + "px";
+ bottom.style.left = "0px";
+ bottom.style.cursor = "n-resize";
+
+ bottom.addEventListener("mousedown", resizeYPositive());
+
+ element.appendChild(bottom);
+
+ const left = document.createElement("div");
+ left.style.width = size + "px";
+ left.style.height = "100%";
+ left.style.backgroundColor = "transparent";
+ left.style.position = "absolute";
+ left.style.top = "0px";
+ left.style.left = -(size / 2) + "px";
+ left.style.cursor = "e-resize";
+
+ left.addEventListener("mousedown", resizeXNegative());
+
+ element.appendChild(left);
+
+ const right = document.createElement("div");
+ right.style.width = size + "px";
+ right.style.height = "100%";
+ right.style.backgroundColor = "transparent";
+ right.style.position = "absolute";
+ right.style.top = "0px";
+ right.style.right = -(size / 2) + "px";
+ right.style.cursor = "e-resize";
+
+ right.addEventListener("mousedown", resizeXPositive());
+
+ element.appendChild(right);
+
+
+ const corner1 = document.createElement("div");
+ corner1.style.width = size + "px";
+ corner1.style.height = size + "px";
+ corner1.style.backgroundColor = "transparent";
+ corner1.style.position = "absolute";
+ corner1.style.top = -(size / 2) + "px";
+ corner1.style.left = -(size / 2) + "px";
+ corner1.style.cursor = "nw-resize";
+
+ corner1.addEventListener("mousedown", resizeXNegative());
+ corner1.addEventListener("mousedown", resizeYNegative());
+
+ element.appendChild(corner1);
+
+ const corner2 = document.createElement("div");
+ corner2.style.width = size + "px";
+ corner2.style.height = size + "px";
+ corner2.style.backgroundColor = "transparent";
+ corner2.style.position = "absolute";
+ corner2.style.top = -(size / 2) + "px";
+ corner2.style.right = -(size / 2) + "px";
+ corner2.style.cursor = "ne-resize";
+
+ corner2.addEventListener("mousedown", resizeXPositive());
+ corner2.addEventListener("mousedown", resizeYNegative());
+
+ element.appendChild(corner2);
+
+ const corner3 = document.createElement("div");
+ corner3.style.width = size + "px";
+ corner3.style.height = size + "px";
+ corner3.style.backgroundColor = "transparent";
+ corner3.style.position = "absolute";
+ corner3.style.bottom = -(size / 2) + "px";
+ corner3.style.left = -(size / 2) + "px";
+ corner3.style.cursor = "sw-resize";
+
+ corner3.addEventListener("mousedown", resizeXNegative());
+ corner3.addEventListener("mousedown", resizeYPositive());
+
+ element.appendChild(corner3);
+
+ const corner4 = document.createElement("div");
+ corner4.style.width = size + "px";
+ corner4.style.height = size + "px";
+ corner4.style.backgroundColor = "transparent";
+ corner4.style.position = "absolute";
+ corner4.style.bottom = -(size / 2) + "px";
+ corner4.style.right = -(size / 2) + "px";
+ corner4.style.cursor = "se-resize";
+
+ corner4.addEventListener("mousedown", resizeXPositive());
+ corner4.addEventListener("mousedown", resizeYPositive());
+
+ element.appendChild(corner4);*/
+
+ const get_int_style = key => parseInt(window.getComputedStyle(element).getPropertyValue(key));
+
+ function resizeXPositive() {
+ let offsetX;
+
+ function dragMouseDown(e) {
+ if(e.button !== 0) return;
+ e = e || window.event;
+ e.preventDefault();
+ const {clientX} = e;
+ offsetX = clientX - element.offsetLeft - get_int_style("width");
+ document.addEventListener("mouseup", closeDragElement);
+ document.addEventListener("mousemove", elementDrag);
+ }
+
+ function elementDrag(e) {
+ const {clientX} = e;
+ let x = clientX - element.offsetLeft - offsetX;
+ if(x < minW) x = minW;
+ else if(x > maxW) x = maxW;
+ element.style.width = x + "px";
+ }
+
+ function closeDragElement() {
+ if(!$this.hasClass("resized-x"))
+ $this.addClass("resized-x");
+
+ document.removeEventListener("mouseup", closeDragElement);
+ document.removeEventListener("mousemove", elementDrag);
+ }
+
+ return dragMouseDown;
+ }
+
+ function resizeYPositive() {
+ let offsetY;
+
+ function dragMouseDown(e) {
+ if(e.button !== 0) return;
+ e = e || window.event;
+ e.preventDefault();
+ const {clientY} = e;
+ offsetY = clientY - element.offsetTop - get_int_style("height");
+
+ document.addEventListener("mouseup", closeDragElement);
+ document.addEventListener("mousemove", elementDrag);
+ }
+
+ function elementDrag(e) {
+ const {clientY} = e;
+ let y = clientY - element.offsetTop - offsetY;
+ if(y < minH) y = minH;
+ element.style.height = y + "px";
+ }
+
+ function closeDragElement() {
+ if(!$this.hasClass("resized-y"))
+ $this.addClass("resized-y");
+
+ document.removeEventListener("mouseup", closeDragElement);
+ document.removeEventListener("mousemove", elementDrag);
+ }
+
+ return dragMouseDown;
+ }
+
+ function resizeXNegative() {
+ let offsetX;
+ let startX;
+ let startW;
+ let maxX;
+
+ function dragMouseDown(e) {
+ if(e.button !== 0) return;
+ e = e || window.event;
+ e.preventDefault();
+ const {clientX} = e;
+ startX = get_int_style("left");
+ startW = get_int_style("width");
+ offsetX = clientX - startX;
+ maxX = startX + startW - minW;
+
+ document.addEventListener("mouseup", closeDragElement);
+ document.addEventListener("mousemove", elementDrag);
+ }
+
+ function elementDrag(e) {
+ const {clientX} = e;
+ let x = clientX - offsetX;
+ let w = startW + startX - x;
+ if(w < minW) w = minW;
+ if(x > maxX) x = maxX;
+ element.style.left = x + "px";
+ element.style.width = w + "px";
+ }
+
+ function closeDragElement() {
+ if(!$this.hasClass("resized-x"))
+ $this.addClass("resized-x");
+
+ document.removeEventListener("mouseup", closeDragElement);
+ document.removeEventListener("mousemove", elementDrag);
+ }
+
+ return dragMouseDown;
+ }
+
+ function resizeYNegative() {
+ let offsetY;
+ let startY;
+ let startH;
+ let maxY;
+
+ function dragMouseDown(e) {
+ if(e.button !== 0) return;
+ e = e || window.event;
+ e.preventDefault();
+ const {clientY} = e;
+ startY = get_int_style("top");
+ startH = get_int_style("height");
+ offsetY = clientY - startY;
+ maxY = startY + startH - minH;
+
+ document.addEventListener("mouseup", closeDragElement, false);
+ document.addEventListener("mousemove", elementDrag, false);
+ }
+
+ function elementDrag(e) {
+ const {clientY} = e;
+ let y = clientY - offsetY;
+ let h = startH + startY - y;
+ if(h < minH) h = minH;
+ if(y > maxY) y = maxY;
+ element.style.top = y + "px";
+ element.style.height = h + "px";
+ }
+
+ function closeDragElement() {
+ if(!$this.hasClass("resized-y"))
+ $this.addClass("resized-y");
+
+ document.removeEventListener("mouseup", closeDragElement);
+ document.removeEventListener("mousemove", elementDrag);
+ }
+
+ return dragMouseDown;
+ }
+ };
+
+ const makeDraggable = () => {
+ const elmnt = $this[0];
+ const boundsX = ($(window).width() - $this.width());
+ let pos1touch, pos2touch, pos3touch, pos4touch;
+ let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
+ const $windowHeader = $this.children(".title-bar");
+
+ if("ontouchstart" in document.documentElement) {
+ pos1touch = 0;
+ pos2touch = 0;
+ pos3touch = 0;
+ pos4touch = 0;
+ }
+ if($windowHeader.length > 0) {
+ $windowHeader.on("mousedown", onDragMouseDown);
+ $windowHeader.on("touchstart", onDragMouseDown);
+ }
+
+ function onDragMouseDown(e) {
+ if(!"ontouchstart" in document.documentElement) {
+ e.preventDefault();
+ }
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+ if("ontouchstart" in document.documentElement) {
+ try {
+ pos3touch = e.touches[0].clientX;
+ pos4touch = e.touches[0].clientY;
+ } catch(error) {}
+ }
+ document.onmouseup = closeDragElement;
+ document.onmousemove = elementDrag;
+ document.ontouchend = closeDragElement;
+ document.ontouchmove = elementDrag;
+ //activeWindow($this);
+ }
+
+ function elementDrag(e) {
+ e.preventDefault();
+ if("ontouchstart" in document.documentElement) {
+ pos1touch = pos3touch - e.touches[0].clientX;
+ pos2touch = pos4touch - e.touches[0].clientY;
+ pos3touch = e.touches[0].clientX;
+ pos4touch = e.touches[0].clientY;
+ elmnt.style.top = (elmnt.offsetTop - pos2touch) + "px";
+ elmnt.style.left = (elmnt.offsetLeft - pos1touch) + "px";
+ } else {
+ pos1 = pos3 - e.clientX;
+ pos2 = pos4 - e.clientY;
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+
+ const posX = (elmnt.offsetLeft - pos1),
+ posY = (elmnt.offsetTop - pos2);
+
+
+ if(posX >= 0 && posY >= 0) {
+ if(posX < boundsX)
+ elmnt.style.left = `${posX}px`;
+ elmnt.style.top = `${posY}px`;
+ } else {
+
+ }
+ }
+ }
+
+ function closeDragElement() {
+ document.onmouseup = null;
+ document.onmousemove = null;
+ document.ontouchend = null;
+ document.ontouchmove = null;
+ }
+ };
+
+ plugin.minimize = () => {
+ const $windowActions = $windowTitleBar.children(".window-actions"),
+ windowActionsWidth = getWidth($windowActions);
+ const $windowTitle = $windowTitleBar.children(".window-title"),
+ windowTitleWidth = getWidth($windowTitle);
+ currentWindowWidth = $this.width();
+ currentWindowHeight = $this.height();
+ $container = $this.parent();
+
+ if($this.hasClass("sgn-window-resizable")) {
+ $windowActions.fadeOut(() => $windowActions.hide());
+ $this.animate({height: (minH - (8 * 2)), width: (minW - (windowActionsWidth - (8 * 2)))}, 200);
+ } else {
+ if($windowFooter.length > 0) {
+ $windowFooter.toggle(() => $windowBody.toggle());
+ } else {
+ $windowBody.toggle();
+ }
+ }
+
+ setTimeout(() => {
+ $this.addClass("minimized");
+
+ const windowTitleBarPadding = getCombinedPadding($windowTitleBar),
+ windowTitleBarOffset = getOffset($windowTitleBar),
+ windowTitleBarHeight = getHeight($windowTitleBar),
+ w = (windowTitleWidth + windowTitleBarOffset);
+
+ $this.animate({width: w}, 200);
+
+ $this.detach().appendTo(".sgn-window-dock");
+ $this = $windowDock.find(`#${guid}`);
+ $windowDock.fadeIn();
+ $windowTitleBar.on("click");
+ }, 200);
+ };
+
+ plugin.restore = () => {
+ $windowTitleBar.off("click");
+ $this.detach().appendTo($container);
+ $this = $container.find(`#${guid}`);
+
+ $windowTitleBar = $this.children(".title-bar");
+ const $windowActions = $windowTitleBar.children(".window-actions"),
+ windowActionsWidth = getWidth($windowActions);
+
+ if($windowDock.children(".sgn-window").length < 1)
+ $windowDock.fadeOut();
+
+ if($this.hasClass("sgn-window-resizable")) {
+ $this.animate({height: currentWindowHeight, width: currentWindowWidth}, 200);
+ setTimeout(() => {
+ $windowActions.show();
+ $windowActions.fadeIn();
+ }, 200);
+ } else {
+ if($windowFooter.length > 0) {
+ $windowBody.toggle(() => {
+ $windowFooter.toggle(() => {
+ $this.width(currentWindowWidth);
+ $this.height(currentWindowHeight);
+ $windowTitleBar.off("click");
+ });
+ });
+ } else {
+ $windowBody.toggle(() => {
+ $this.width(currentWindowWidth);
+ $this.height(currentWindowHeight);
+ $windowTitleBar.off("click");
+ });
+ }
+ }
+
+ $this.removeClass("minimized");
+ };
+
+ plugin.maximize = () => {
+ $this.addClass("maximized");
+ };
+
+ plugin.compress = () => {
+ $this.removeClass("maximized fullscreen");
+ };
+
+ plugin.toggleFullscreen = () => {
+ const $windowTitle = $windowTitleBar.children(".window-title"),
+ $windowActions = $windowTitleBar.children(".window-actions");
+ const $closeBtn = $windowActions.children(".sgn-window-closeBtn"),
+ $fullscreenBtn = $windowActions.children(".sgn-window-fullscreenBtn"),
+ $maximizeBtn = $windowActions.children(".sgn-window-maximizeBtn"),
+ $minimizeBtn = $windowActions.children(".sgn-window-minimizeBtn");
+ const elem = $this[0],
+ windowTitleBarPadding = getPadding($windowTitleBar, "x"),
+ windowTitleBarHeight = getHeight($windowTitleBar);
+ let timeout;
+
+ if(!document.fullscreenElement) {
+ elem.requestFullscreen().then(r => {
+ $closeBtn.hide();
+ $maximizeBtn.hide();
+ $minimizeBtn.hide();
+
+ $this.addClass("fullscreen");
+
+ let titleBar = true;
+ $(window).on("mouseover touchstart", (e) => {
+ const clientY = e.clientY;
+
+ if(clientY < windowTitleBarHeight) {
+ e.stopPropagation();
+ clearTimeout(timeout);
+ if(!titleBar) {
+ $windowTitleBar.show();
+ $windowTitleBar.animate({height: `${windowTitleBarHeight}px`, padding: `${windowTitleBarPadding}px`}, 500);
+ titleBar = true;
+ }
+ } else {
+ if(titleBar) {
+ timeout = setTimeout(() => $windowTitleBar.animate({height: "0px", padding: "0px"}, 500, () => $windowTitleBar.fadeOut(100)), 2000);
+ titleBar = false;
+ }
+ }
+ });
+ }).catch(e => alert(`Error attempting to enable full-screen mode: ${e.message} (${e.name})`));
+ } else {
+ clearTimeout(timeout);
+ $(window).off("mouseover touchstart");
+ $windowTitleBar.show().animate({height: `${windowTitleBarHeight}px`, padding: `${windowTitleBarPadding}px`}, 500);
+ $windowTitleBar.removeAttr("style");
+
+ document.exitFullscreen().then(r => {
+ $closeBtn.show();
+ $maximizeBtn.show();
+ $minimizeBtn.show();
+
+ $this.removeClass("fullscreen");
+ });
+ }
+ };
+
+ plugin.open = () => {
+ if($this.hasClass("closed"))
+ $this.toggle(() => $this.removeClass("closed"));
+ };
+
+ plugin.close = () => {
+ if(!$this.hasClass("closed"))
+ $this.toggle(() => $this.addClass("closed"));
+ };
+
+ plugin.toggleWindow = () => {
+ if(!$this.hasClass("closed"))
+ plugin.close();
+ else
+ plugin.open();
+ };
+
+ plugin.remove = () => $this.toggle(() => $this.remove());
+
+ if($this.data("SGNWindow") === undefined)
+ init();
+ };
+
+ $.fn.SGNWindow = function() {
+ const _this = this;
+ const $this = $(_this);
+
+ const init = () => new SGNWindow($this);
+
+ /***
+ * Minimize the currently selected SGNWindow instance.
+ *
+ * @returns {jQuery.SGNWindow}
+ */
+ this.minimize = function() {
+ const plugin = $(this).data("SGNWindow");
+ if(plugin !== undefined)
+ plugin.minimize();
+
+ return this;
+ };
+
+ /***
+ * Restore the currently selected SGNWindow instance.
+ *
+ * @returns {jQuery.SGNWindow}
+ */
+ this.restore = function() {
+ const plugin = $(this).data("SGNWindow");
+ if(plugin !== undefined)
+ plugin.restore();
+
+ return this;
+ };
+
+ /***
+ * Maximize the currently selected SGNWindow instance.
+ *
+ * @returns {jQuery.SGNWindow}
+ */
+ this.maximize = function() {
+ const plugin = $(this).data("SGNWindow");
+ if(plugin !== undefined)
+ plugin.maximize();
+
+ return this;
+ };
+
+ /***
+ * Restore down to default window mode for the currently selected SGNWindow instance.
+ *
+ * @returns {jQuery.SGNWindow}
+ */
+ this.compress = function() {
+ const plugin = $(this).data("SGNWindow");
+ if(plugin !== undefined)
+ plugin.compress();
+
+ return this;
+ };
+
+ /***
+ * Toggle full-screen mode of the currently selected SGNWindow instance.
+ *
+ * @returns {jQuery.SGNWindow}
+ */
+ this.toggleFullscreen = function() {
+ const plugin = $(this).data("SGNWindow");
+ if(plugin !== undefined)
+ plugin.toggleFullscreen();
+
+ return this;
+ };
+
+ /***
+ * Open the currently selected SGNWindow instance.
+ *
+ * @returns {jQuery.SGNWindow}
+ */
+ this.open = function() {
+ const plugin = $(this).data("SGNWindow");
+ if(plugin !== undefined)
+ plugin.open();
+
+ return this;
+ };
+
+ /***
+ * Close the currently selected SGNWindow instance.
+ *
+ * @returns {jQuery.SGNWindow}
+ */
+ this.close = function() {
+ const plugin = $(this).data("SGNWindow");
+ if(plugin !== undefined)
+ plugin.close();
+
+ return this;
+ };
+
+ /***
+ * Toggle between open/close operations of the currently selected SGNWindow instance.
+ *
+ * @returns {jQuery.SGNWindow}
+ */
+ this.toggleWindow = function() {
+ const plugin = $(this).data("SGNWindow");
+ if(plugin !== undefined)
+ plugin.toggleWindow();
+
+ return this;
+ };
+
+ if($this.data("SGNWindow") === undefined)
+ init();
+
+ return this;
+ };
+
+ $(function() {
+ const $windowToggles = $.select($("[sgn-window]"), $("[data-sgn-window]"), $("[data-window]"));
+ const $windows = $(".sgn-window");
+
+ if($windows.length > 0) {
+ $windows.each(function() {
+ const $this = $(this);
+ $this.SGNWindow();
+ });
+ }
+ if($windowToggles.length > 0) {
+ $windowToggles.each(function() {
+ $(this).on("click", function(e) {
+ e.preventDefault();
+
+ const $this = $(this),
+ target = select($this.attr("sgn-window"), $this.attr("data-sgn-window"), $this.attr("data-window")),
+ $target = $(target);
+
+ if($target.length > 0) {
+ $target.SGNWindow().open();
+ }
+ });
+ });
+ }
+ });
+
+ return this;
+}(window, document, jQuery));
\ No newline at end of file