/*
* Merger UI V0.3 t156
* Copyright 2016 XWolfOVerride@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var merger = new function () {
/**
* merger system variables
*/
var sys = {
_type: "system",
icon: "data:image/gif;base64,R0lGODlhIAAgAOMAAP///zOZ/47N8FxqpgAAAMzM/7+/v9nu+QBjpAA9hP///////////////////////yH5BAEKAA8ALAAAAAAgACAAAATq8MlJH7k16y3JEQXGjZVXBGBIkKQpoEIqsuVRxHAsr3Rn6zndjuYCCo8F1ahoPCJDG2bTKbTxKNIpVWAbXH03atDZ9ZYKh49zXC0M3l/LKZA+Bthc99uMnd/rLzhBZXtxBH53dGpAKISFZ4mJCIpHjo99kQGTiWmdbgkJe3AGmJKZdwUPem+ghQavHX6bpyABoqyhBK+wh3ezpwGrtwMJurtymsCRwsPGpHK/ysyizhME0dLDo7DWBMqZ017HFQYX36jN4xrl3tnU6hzswMLVPfLLrtw9EvfB28/7KMhzUy9gBnYFDa6DtyECADs=",
ver: "0.3",
color: {
frame: "teal",
client: "white",
// frame:"orange",
framecontrast: "white",
text: "black",
windowtitle: "black",
selection: "lightskyblue",
},
icons: {}
};
/**
* merge method the root of all merger framework
*/
if (!Object.prototype.merge)
Object.defineProperty(Object.prototype, "merge", {
writable: true,
value: function (src) {
if (!src)
return this;
for (var key in src)
this[key] = src[key];
return this;
}
});
var ui = {
app: {},
w: {},
menu: {},
};
/**
* Toolbar clock logic
*/
function clockTick() {
var now = new Date(), h = now.getHours(), m = now.getMinutes();
if (h < 10)
h = "0" + h;
if (m < 10)
m = "0" + m;
ui.menu.time.innerText = h + ":" + m;
}
// ---- Tools
/**
* Tool for trigger creation
* USE: insist(<controller>)
*
* controller.when: function to check triggering.
* The first time the when returns true the "do"" method will be executed
* controller.do: function with logic to execute when "when" method returns true
* controller.each: (optional) integer with milliseconds to wait between checks.
* If not set, a continious check will be done. WARNING: this can consume a lot of CPU.
*/
function insist(ctrl) {
var controller = {
when: function () {
return true;
},
"do": function () {
},
each: undefined
}.merge(ctrl);
controller.check = function () {
if (!this.when())
setTimeout(this.check.bind(this), this.each);
else
this["do"]();
}
controller.check();
}
// ---- UI implementation
// ----------------------
/**
* Tool for creating tags
*/
function mkTag(tag) {
return document.createElement(tag);
}
/**
* Tool for injecting CSS rules
*/
function mkCSS(css) {
style = mkTag('style');
if (style.styleSheet)
style.styleSheet.cssText = css;
else
style.appendChild(document.createTextNode(css));
document.getElementsByTagName('head')[0].appendChild(style);
}
/**
* Function to merge control definition preserving style
*/
function mkDefinition(a, b) {
if (a.style)
b.style = a.style.merge(b.style);
return a.merge(b);
}
/**
* Create a standard SVG icon
*/
function mkIcon(color, text) {
return "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128' version='1.1'><circle cx='64' cy='64' r='62' id='c' style='fill:" + color + ";fill-opacity:1' /><text text-anchor='middle' alignment-baseline='alphabetic' style='font-weight:bold;font-size:90px;font-family:sans-serif;fill:#ffffff;stroke:none;' x='64' y='95'>" + text + "</text></svg>";
}
/**
* Return current configured help icon
*/
function getHelpIcon() {
return sys.icons.help ? sys.icons.help : mkIcon(sys.color.frame, '?');
}
/**
* Drag'N'Drop system
*/
var DragNDrop = new (function () {
var item, point;
this.merge({
drag: function (i, x, y) {
if (item)
this.drop();
item = i;
point = { x: x, y: y };
},
drop: function (ctl) {
if (!ctl)
item = null;
else if (item == ctl)
item = null;
},
dragging: function () {
return item ? true : false;
},
update: function (x, y) {
item.merge({
top: y - point.y,
left: x - point.x,
})
}
});
})();
/**
* Open merger desktop.
* The first time finish the basic layout creation
*/
function openDesktop() {
if (!ui.dsk.merger_init) {
ui.dsk.merger_init = true;
ui.dsk.onmousemove = function (e) {
if (DragNDrop.dragging())
DragNDrop.update(e.clientX, e.clientY);
}
ui.dsk.onclick = function (e) {
if (ui.menu.current) {
ui.menu.current.close(true);
delete (ui.menu.current);
}
if (ui.menu.showMenu) {
ui.menu.showMenu.popup();
delete (ui.menu.showMenu);
}
}
ui.menu.style.merge({
position: "absolute",
zIndex: 512,
top: 0,
left: 0,
width: "100%",
height: "21px",
padding: "0px 5px 0px 5px",
backgroundColor: "white",
borderBottom: "1px solid " + sys.color.frame,
boxSizing: "border-box",
});
ui.menu.sysMenu = merger.ui.menuItem("sysMenu", {
icon: sys.icon,
style: {
float: "left",
padding: "2px 4px 2px 9px",
},
parentControl: sys,
});
ui.menu.client = mkTag("div");
ui.menu.client.style.merge({
float: "left",
fontFamily: "Arial",
fontSize: "14px",
lineHeight: "16px",
});
ui.menu.time = mkTag("div");
ui.menu.time.style.merge({
float: "right",
fontFamily: "Arial",
fontSize: "14px",
lineHeight: "16px",
padding: "2px 4px 2px 4px",
color: sys.framecolor,
});
ui.menu.appendChild(ui.menu.sysMenu);
ui.menu.appendChild(ui.menu.client);
ui.menu.appendChild(ui.menu.time);
ui.menu.style.merge({
userSelect: "none",
})
clockTick();
setInterval(clockTick, 60000);
}
document.body.appendChild(ui.dsk);
}
/**
* Base control creation
*/
function control(type, id, def, c) {
if (!c)
c = mkTag("div");
c.setAttribute("merger_type", type);
c.setAttribute("id", id);
c.setAttribute("name", id);
c._type = type;
c.content = {};
c.getId = function () {
return id;
}
c.setTop = function (val) {
def.top = val;
this.style.top = val + "px";
}
c.setLeft = function (val) {
def.left = val;
this.style.left = val + "px";
}
c.setWidth = function (val) {
def.width = val;
this.style.width = val + "px";
}
c.setHeight = function (val) {
def.height = val;
this.style.height = val + "px";
}
c.setVisible = function (val) {
this.style.display = val ? "" : "none";
_visible = val;
if (val && this.onShow)
this.onShow();
if (val && this.onHide)
this.onHide();
if (this.onVisibilityChanged)
this.onVisibilityChanged();
}
c.getVisible = function () {
return this.style.display != "none";
}
c.show = function () {
this.setVisible(true);
}
c.hide = function () {
this.setVisible(false);
}
c.append = function (control) {
this.appendChild(control);
this.content[control.getId()] = control;
control.parentControl = this;
}
c.setContent = function (ctt) {
for (var i = 0; i < ctt.length; i++)
this.append(ctt[i]);
}
c.setStyle = function (style) {
this.style.merge(style);
}
c.getWindow = function () {
var win = this;
while (win && win._type != "window")
win = win.parentControl;
return win;
}
c.getApp = function () {
return this.getWindow().parent;
}
c.merge = function (def) {
var settings = {};
for (var key in def) {
var mdata = def[key];
if (typeof mdata == "function")
c[key] = def[key];
else {
var fname = "set" + key[0].toUpperCase() + key.substring(1);
var fnc = c[fname];
settings[key] = {
f: (fnc && typeof fnc == "function") ? fnc.bind(this) : null, d: mdata
};
}
}
for (var key in settings) {
var o = settings[key];
if (o.f)
o.f(o.d);
else
this[key] = o.d;
}
}
c.merge(mkDefinition({
style: {
position: "absolute",
},
anchor: { top: true, right: false, bottom: false, left: true },
}, def));
if (c.onControlCreate)
c.onControlCreate();
return c;
}
/**
* Menu Item control creation
*/
function menuItem(id, def) {
var client, dirty = true, icon, openMark, textElement, m = control("menu", id, mkDefinition({
style: {
position: "",
padding: "2px 9px 2px 9px",
color: sys.color.windowtitle,
},
setText: function (text) {
if (!textElement)
this.appendChild(textElement = document.createTextNode(text));
else textElement.textContent = text;
},
setIcon: function (src) {
if (src) {
if (!icon) {
this.appendChild(icon = mkTag("img"));
icon.style.merge({
width: "16px",
height: "16px",
float: "left",
padding: "0 5px 0 0",
});
} else
icon.style.display = "";
icon.src = src;
} else if (icon) icon.style.display = "none";
},
showMoreMark: function () {
if (!openMark) {
this.appendChild(openMark = mkTag("div"));
openMark.style.merge({
float: "right",
padding: "0 0 0 5px",
});
openMark.appendChild(document.createTextNode("\u25B6"));
} else openMark.style.display = "";
},
hideMoreMark: function () {
if (openMark)
openMark.style.display = "none";
},
popup: function () {
if (this.parentControl._type == "menu")
this.parentControl.popup();
if (client && client._visible)
return;
if (!client) {
client = control("menuClient", id + "_client", {
style: {
background: sys.color.client,
border: "1px solid " + sys.color.frame,
fontFamily: "Arial",
fontSize: "14px",
}
});
ui.dsk.appendChild(client);
}
var bounds = this.getBoundingClientRect();
if (this.parentControl._type == "menu")
client.merge({
top: bounds.top,
left: bounds.left + bounds.width,
});
else
client.merge({
top: bounds.top + bounds.height,
left: bounds.left,
});
while (client.lastChild)
client.removeChild(client.lastChild);
for (i in this.items) {
var m = this.items[i];
m.application = this.applicaiton;
m.parentControl = this;
if (m.items)
m.showMoreMark();
client.appendChild(m);
}
ui.menu.current = this;
client.show();
},
close: function (all) {
if (client)
client.hide();
if (ui.menu.current == this)
ui.menu.current = null;
if (all && this.parentControl._type == "menu")
this.parentControl.close(all);
},
onmouseenter: function () {
this.style.backgroundColor = sys.color.frame;
this.style.color = sys.color.framecontrast;
},
onmouseleave: function () {
this.style.backgroundColor = "";
this.style.color = sys.color.windowtitle;
},
onclick: function () {
if (this.items)
ui.menu.showMenu = this;
else if (this.onClick)
this.onClick.apply(this, arguments);
}
}, def));
return m;
}
/**
* Menu separator control creation
*/
function menuSeparator(id, def) {
var line, m = menuItem(id, {
setText: undefined,
setIcon: undefined,
showMoreMark: undefined,
onmouseenter: undefined,
onmouseleave: undefined,
onclick: function () {
ui.menu.showMenu = this.parentControl;
},
onControlCreate: function () {
this.appendChild(line = document.createElement("div"));
line.style.merge({
width: "100%",
height: "1px",
background: sys.color.frame,
});
},
})
return m;
}
/**
* Window control creation
*/
function window(id, def) {
if (typeof (def) != "object")
def = {};
if (def.height == undefined)
def.height = 300;
if (def.width == undefined)
def.width = 450;
if (def.top == undefined)
def.top = (document.body.clientHeight - def.height) / 2;
if (def.left == undefined)
def.left = (document.body.clientWidth - def.width) / 2;
if (!def.content)
def.content = [];
var w, dw = def.width, dh = def.height;
delete (def.width);
delete (def.height);
if (dw == undefined)
dw = 300;
if (dh == undefined)
dh = 200;
// Window Title
var wt = control("titlebar", "windowtitle", {
visible: !def.hideTitle,
style: {
textAlign: "right",
height: "20px",
boxSizing: "border-box",
fontFamily: "Chicago, Verdana",
fontSize: "13px",
lineHeight: "20px",
position: "relative",
paddingRight: "7px",
userSelect: "none",
color: sys.color.windowtitle,
},
content: [
control("windowclosebutton", "closeButton", {
visible: !def.hideCloseButton,
style: {
top: "0",
left: "0",
width: "25px",
height: "20px",
lineHeight: "20px",
border: "0",
backgroundColor: sys.color.frame,
padding: 0,
boxSizing: "content-box",
color: "white",
},
setText: function (text) {
this.innerText = text;
},
onclick: function () {
w.close();
},
text: "X",
}, document.createElement("button"))
],
setTitle: function (title) {
if (!this._textNode) {
this._textNode = document.createTextNode("");
this.appendChild(this._textNode);
}
this._textNode.textContent = title;
},
onmousedown: function (e) {
DragNDrop.drag(this.parentControl, e.offsetX, e.offsetY);
},
onmouseup: function (e) {
DragNDrop.drop(this.parentControl);
}
});
// Window client
var wc = control("windowclient", "client", {
content: def.content,
style: { position: "relative", margin: "5px" },
width: dw,
height: dh,
});
def.content = [wt, wc];
// Window root
w = control("window", id, mkDefinition({
width: dw + 10,
height: dh + 30,
setTitle: function (title) {
wt.setTitle(title);
},
setTop: function (val) {
if (val < 20)
val = 20;
def.top = val;
this.style.top = val + "px";
},
style: {
backgroundColor: "white",
border: "1px solid " + sys.color.frame,
boxShadow: "0px 0px 3px rgba(0,0,0,0.5)",
overflow: "hidden",
},
close: function () {
var close = true;
if (this.onClose)
close = this.onClose();
if (close)
this.hide();
},
}, def));
ui.dsk.appendChild(w);
ui.w[id] = w;
w.client = w.content.client;
w.titlebar = w.content.titlebar;
w.content = w.client.content;
return w;
}
/**
* Picture control creation
*/
function picture(id, def) {
var c = control("picture", id, mkDefinition({
setSrc: function (src) {
this.src = src;
}
}, def), mkTag("img"));
return c;
}
/**
* Label control creation
*/
function label(id, def) {
var c = control("label", id, mkDefinition({
setText: function (text) {
this.innerText = text;
}
}, def));
if (def.text !== undefined)
c.setText(def.text);
return c;
}
/**
* TextBox control creation
*/
function textbox(id, def) {
var c = control("textbox", id, mkDefinition({
setText: function (value) {
this.value = value;
},
style: {
fontSize: "10px",
border: "1px solid " + sys.framecolor,
}
}, def), mkTag(def.multiple ? "textarea" : "input"));
return c;
}
/**
* Button control creation
*/
function button(id, def) {
var c = control("button", id, mkDefinition({
setText: function (text) {
this.innerText = text;
},
style: {
border: "0",
borderRadius: "7px",
background: sys.color.frame,
color: sys.color.framecontrast,
height: "20px",
},
onmousedown: function () {
this.style.merge({
background: sys.color.framecontrast,
color: sys.color.frame,
})
},
onmouseup: function () {
this.style.merge({
background: sys.color.frame,
color: sys.color.framecontrast,
})
},
onmouseleave: function () {
this.style.merge({
background: sys.color.frame,
color: sys.color.framecontrast,
})
},
}, def), mkTag("button"));
c.setText(def.text);
c.addEventListener("click", function () {
if (this.onClick)
this.onClick.apply(this, arguments)
}, false);
return c;
}
/**
* List control creation
*/
function list(id, def) {
var c = control("list", id, def);
c.style.border = "1px solid red";
return c;
}
/**
* Component path getter
*/
function get(id) {
return ui.w[id];
}
// ---- Kernel implementation
// --------------------------
function kSwitchApp(app) {
var i, m, a;
ui.menu.sysMenu.setIcon(app.icon ? app.icon : sys.icon);
ui.menu.sysMenu.items = [];
if (app.onAbout) {
ui.menu.sysMenu.items.push(
m = merger.ui.menuItem("sys_about" + i, {
icon: getHelpIcon(),
text: "About " + app.title,
onClick: function () {
app.onAbout();
}
})
);
m.parentControl = ui.menu.sysMenu;
ui.menu.sysMenu.items.push(m = menuSeparator("sys_sep1"));
m.parentControl = ui.menu.sysMenu;
}
for (i in ui.app) {
a = ui.app[i];
ui.menu.sysMenu.items.push(
m = merger.ui.menuItem("sys_app_" + i, {
icon: a.icon ? a.icon : sys.icon,
text: a.title,
_app: a,
onClick: function () {
kSwitchApp(this._app);
}
})
);
m.parentControl = ui.menu.sysMenu;
}
while (ui.menu.client.lastChild)
ui.menu.client.removeChild(ui.menu.client.lastChild);
for (i in app.menu)
ui.menu.client.appendChild(app.menu[i]);
}
/**
* Application creation
*/
function app(id, def) {
if (ui.app[id])
throw new Error("Application '" + id + "' already exists");
var a = {
_type: "app",
getId: function () {
return id;
},
title: id,
windows: {},
menu: {},
onLoad: function () {
},
show: function () {
openDesktop();
if (this.mainWindow)
this.mainWindow.show();
},
focus: function () {
this.show();
kSwitchApp(this);
if (this.mainWindow)
this.mainWindow.focus();
if (this.onEnter)
this.onEnter();
}
}, windows = def.windows, menu = def.menu, i;
ui.app[id] = a;
delete (def.windows);
delete (def.menu);
a.merge(def);
if (windows)
for (i = 0; i < windows.length; i++) {
var win = windows[i];
a.windows[win.getId()] = win;
win.application = win.parentControl = a;
}
if (menu)
for (i = 0; i < menu.length; i++) {
var m = menu[i];
a.menu[m.getId()] = m;
m.application = m.parentControl = a;
m.style.display = "inline-block";
m._root = true;
}
// Drop MainWindow feature
if (a.mainWindow)
a.mainWindow = a.windows[a.mainWindow];
if (a.onLoad)
a.onLoad();
return a;
}
function enter() {
openDesktop();
}
function leave() {
if (document.body.contains(ui.dsk))
document.body.removeChild(ui.dsk);
}
/**
* merger API
*/
// -- Kernel
this.merge({
app: app,
enter: enter,
leave: leave,
version: sys.ver,
// -- UI
ui: {
window: window,
get: get,
list: list,
label: label,
textbox: textbox,
button: button,
picture: picture,
menuItem: menuItem,
menuSeparator: menuSeparator,
},
media: {
createIcon: mkIcon,
},
conf: {
color: sys.color,
icon: sys.icons,
}
});
/**
* Initializes merger subsystem
*/
function kInit() {
ui.dsk = mkTag("div");
ui.dsk.id = "Merger::Desktop";
ui.dsk.style.merge({
position: "absolute",
zIndex: 510,
top: 0,
left: 0,
width: "100%",
height: "100%",
fontFamily: "Verdana",
fontSize: "10px",
overflow: "hidden",
backgroundColor: "rgba(128,128,128,0.3)",
});
ui.menu = mkTag("div");
ui.dsk.appendChild(ui.menu);
}
kInit();
}
/**
* Test app
*/
merger.app("merger", {
title: "Merger Sample Application (Kill)"
});