diff --git a/build/css/ox.ui.css b/build/css/ox.ui.css index 49aee721..9b99da6d 100644 --- a/build/css/ox.ui.css +++ b/build/css/ox.ui.css @@ -296,6 +296,188 @@ OxRange margin-top: -1px; } +/* +================================================================================ +Menus +================================================================================ +*/ + +.OxMenu { + position: absolute; + display: none; + z-index: 11; + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -moz-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.75); + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.75); +} +.OxMenu.OxRight { + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} +.OxMenu .OxTop { + height: 4px; + background: rgba(48, 48, 48, 0.96); +} +.OxMenu.OxRight .OxTop { + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; +} +.OxMenu .OxBottom { + height: 4px; + background: rgba(48, 48, 48, 0.96); + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; +} +.OxMenu .OxContainer { + background: transparent; + overflow: hidden; +} +.OxMenu .OxContent { + position: relative; + border-collapse: collapse; + border-spacing: 0; +} +.OxMenu .OxItem { + background: rgba(48, 48, 48, 0.96); + cursor: pointer; +} +.OxMenu.OxLarge .OxItem { + height: 20px; +} +.OxMenu.OxMedium .OxItem { + height: 16px; +} +.OxMenu.OxSmall .OxItem { + height: 12px; +} +.OxMenu .OxItem.OxDisabled { + cursor: default; +} +.OxMenu .OxItem.OxSelected { + background: rgba(64, 64, 64, 0.96); +} +.OxMenu.OxLarge .OxItem .OxCell { + height: 20px; + font-size: 14px; +} +.OxMenu.OxMedium .OxItem .OxCell { + height: 16px; + font-size: 11px; +} +.OxMenu.OxSmall .OxItem .OxCell { + height: 12px; + font-size: 9px; +} +.OxMenu .OxItem.OxDisabled .OxCell { + color: rgb(80, 80, 80); +} +.OxMenu .OxItem .OxCell.OxStatus { + padding-left: 4px; + text-align: right; +} +.OxMenu .OxItem .OxCell.OxIcon { + padding-left: 4px; +} +.OxMenu .OxItem .OxCell.OxIcon img { + position: relative; + top: 2px; +} +.OxMenu.OxLarge .OxItem .OxCell.OxIcon img { + width: 16px; + height: 16px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} +.OxMenu.OxMedium .OxItem .OxCell.OxIcon img { + width: 12px; + height: 12px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; +} +.OxMenu.OxSmall .OxItem .OxCell.OxIcon img { + width: 8px; + height: 8px; + -moz-border-radius: 1px; + -webkit-border-radius: 1px; +} +.OxMenu .OxItem .OxCell.OxTitle { + padding-left: 4px; + white-space: nowrap; +} +.OxMenu .OxItem .OxCell.OxModifiers { + padding-left: 4px; + text-align: right; +} +.OxMenu .OxItem .OxCell.OxKey { + padding-right: 8px; +} +.OxMenu .OxItem .OxCell.OxSubmenu { + padding-right: 8px; + text-align: right; +} +.OxMenu.OxLarge .OxItem .OxCell.OxSubmenu { + font-size: 10px; +} +.OxMenu.OxMedium .OxItem .OxCell.OxSubmenu { + font-size: 8px; +} +.OxMenu.OxSmall .OxItem .OxCell.OxSubmenu { + font-size: 6px; +} +.OxMenu.OxLarge .OxItem .OxCell.OxStatus, +.OxMenu.OxLarge .OxItem .OxCell.OxKey, +.OxMenu.OxLarge .OxItem .OxCell.OxSubmenu { + width: 12px; +} +.OxMenu.OxMedium .OxItem .OxCell.OxStatus, +.OxMenu.OxMedium .OxItem .OxCell.OxKey, +.OxMenu.OxMedium .OxItem .OxCell.OxSubmenu { + width: 10px; +} +.OxMenu.OxSmall .OxItem .OxCell.OxStatus, +.OxMenu.OxSmall .OxItem .OxCell.OxKey, +.OxMenu.OxSmall .OxItem .OxCell.OxSubmenu { + width: 8px; +} +.OxMenu .OxSpace { + height: 4px; + background: rgba(48, 48, 48, 0.96); +} +.OxMenu .OxLine { + height: 1px; + background: rgba(64, 64, 64, 0.96); +} +.OxMenu .OxScrollBar { + background: rgba(48, 48, 48, 0.96); + text-align: center; + cursor: pointer; + display: none; +} +.OxMenu.OxLarge .OxScrollBar { + height: 16px; + padding-top: 4px; + font-size: 10px; +} +.OxMenu.OxMedium .OxScrollBar { + height: 13px; + padding-top: 3px; + font-size: 8px; +} +.OxMenu.OxSmall .OxScrollBar { + height: 10px; + padding-top: 2px; + font-size: 6px; +} +.OxMenu .OxScrollBar.OxSelected { + background: rgba(64, 64, 64, 0.96); +} + + /* ================================================================================ Panels diff --git a/build/js/ox.ui.js b/build/js/ox.ui.js index e3cb81ab..96f7dd0b 100644 --- a/build/js/ox.ui.js +++ b/build/js/ox.ui.js @@ -47,7 +47,59 @@ requires }(), path: $("script[src*=ox.ui.js]").attr("src") .replace("js/ox.ui.js", ""), - stack: [] + stack: [], // fixme: used? + symbols: { // fixme: make lowercase + alt: "\u2325", + apple: "\uF8FF", + arrow_down: "\u2193", + arrow_left: "\u2190", + "arrow right": "\u2192", + "arrow up": "\u2191", + "backspace": "\u232B", + "backup": "\u2707", + "ballot": "\u2717", + "black star": "\u2605", + "burn": "\u2622", + "caps lock": "\u21EA", + "check": "\u2713", + "CLEAR": "\u2327", + "CLICK": "\uF803", + "CLOSE": "\u2715", + "COMMAND": "\u2318", + "CONTROL": "\u2303", + "CUT": "\u2702", + "DELETE": "\u2326", + "DIAMOND": "\u25C6", + "EDIT": "\uF802", + "EJECT": "\u23CF", + "ESCAPE": "\u238B", + "END": "\u2198", + "ENTER": "\u2324", + "FLY": "\u2708", + "GEAR": "\u2699", + "HOME": "\u2196", + "INFO": "\u24D8", + "NAVIGATE": "\u2388", + "OPTION": "\u2387", + "PAGE UP": "\u21DE", + "PAGE DOWN": "\u21DF", + "REDO": "\u21BA", + "RETURN": "\u21A9", + "SELECT": "\u21D5", + "SHIFT": "\u21E7", + "SOUND": "\u266B", + "SPACE": "\u2423", + "TAB": "\u21E5", + "TRASH": "\u267A", + "TRIANGLE DOWN": "\u25BC", + "TRIANGLE LEFT": "\u25C0", + triangle_right: "\u25BA", + "TRIANGLE UP": "\u25B2", + "UNDO": "\u21BB", + "VOLTAGE": "\u26A1", + "WARNING": "\u26A0", + "WHITE STAR": "\u2606" + } }, $window, $document, $body; @@ -130,6 +182,7 @@ requires ---------------------------------------------------------------------------- Ox.Event ---------------------------------------------------------------------------- + naming convention for event/trigger verb.id.namespace, i.e. verb.sourceId.targetId (?) ... @@ -149,6 +202,9 @@ requires */ // use dom elements / jquery instead + + $eventHandler = $("
").addClass("OxLine", { + attr: { + colspan: 5 + } + }) + ); } function constructScrollbar(direction) { - + var interval; + return $("", { + addClass: "OxScrollBar Ox" + Ox.toTitleCase(direction), + html: oxui.symbols["triangle_" + direction], + click: function() { // fixme: do we need to listen to click event? + return false; + }, + mousedown: function() { + scrollSpeed = 2; + return false; + }, + mouseenter: function() { + var $otherScrollbar = that.$scrollbars[direction == "up" ? "down" : "up"]; + $(this).addClass("OxSelected"); + if ($otherScrollbar.is(":hidden")) { + $otherScrollbar.show(); + that.$container.height(that.$container.height() - itemHeight); + if (direction == "down") { + that.$content.css({ + top: -itemHeight + "px" + }); + } + } + scrollMenu(direction == "up" ? -1 : 1); + interval = setInterval(function() { + scrollMenu(direction == "up" ? -1 : 1); + }, 100); + }, + mouseleave: function() { + $(this).removeClass("OxSelected"); + clearInterval(interval); + }, + mouseup: function() { + scrollSpeed = 1; + return false; + } + }); } function constructSpace() { - + return $(" | |||||
").addClass("OxSpace", { + attr: { + colspan: 5 + } + }) + ); } function getElement(id) { return $("#" + Ox.toCamelCase(options.id + "/" + id)); } - function parseShortcut() { - - } - - function scroll(speed) { + function scrollMenu(speed) { + var containerHeight = that.$container.height(), + contentHeight = that.$content.height(), + top = parseInt(that.$content.css("top")) || 0, + min = containerHeight - contentHeight + itemHeight, + max = 0; + top += speed * scrollSpeed * -itemHeight; + if (top <= min) { + top = min; + that.$scrollbars.down.hide().trigger("mouseleave"); + that.$container.css({ + height: (containerHeight + itemHeight) + "px" + }); + that.$items[that.$items.length - 1].trigger("mouseover"); + } else if (top >= max - itemHeight) { + top = max; + that.$scrollbars.up.hide().trigger("mouseleave"); + that.$container.css({ + height: (containerHeight + itemHeight) + "px" + }); + that.$items[0].trigger("mouseover"); + } + that.$content.css({ + top: top + "px" + }); } @@ -1675,62 +1786,53 @@ requires } - that.check = function(id) { - - }; - - that.disable = function(id) { - - }; - - that.enable = function(id) { - - }; - that.hideMenu = function() { - - }; - - that.insertItemAfter = function(id, item) { - - }; - - that.insertItemBefore = function(id, item) { - - }; - - that.removeItem = function(id) { - + $.each(that.$submenus, function(i, $submenu) { + if (!$submenu.is(":hidden")) { + $submenu.hideMenu(); + return false; + } + }); + // fixme: scroll menu back up! + that.hide(); }; that.showMenu = function() { - + Ox.print("showMenu") + var offset = that.$element.offset(), + padding = { + left: parseInt(that.$element.css("padding-left")), + top: parseInt(that.$element.css("padding-top")), + }, + width = that.$element.width(), + height = that.$element.height(), + left = offset.left + self.options.offset.left + (self.options.side == "bottom" ? 0 : width + padding.left), + top = offset.top + self.options.offset.top + (self.options.side == "bottom" ? height + padding.top : 0), + maxHeight = Math.floor(($window.height() - top - 12) / itemHeight) * itemHeight; + that.css({ + left: left + "px", + top: top + "px" + }).show(); + that.parent().length || that.appendTo($body); + if (height > maxHeight) { + that.$container.height(maxHeight - itemHeight); + that.$scrollbars.down.show(); + } }; - that.toggleChecked = function(id) { - - }; - - that.toggleDisabled = function(id) { - - }; - - that.toggleTitle = function(id) { - - }; - - that.uncheck = function(id) { - + that.toggleMenu = function() { + Ox.print("toggleMenu") + that.is(":hidden") ? that.showMenu() : that.hideMenu(); }; return that; - } + }; Ox.MenuItem = function(options, self) { var self = self || {}, - that = new Ox.Element("tr") + that = new Ox.Element("tr", self) .defaults({ bind: [], checked: false, @@ -1739,28 +1841,146 @@ requires group: "", icon: "", id: "", - items: [], - menu: "", - shortcut: { - modifiers: [], - key: "" - }, - title: "", + keyboard: "", + menu: null, // fixme: is passing the menu to 100s of menu items really memory-neutral? + submenu: null, + title: [], }) - .options(options); + .options($.extend(options, { + keyboard: parseKeyboard(options.keyboard || self.defaults.keyboard), + title: Ox.makeArray(options.title || self.defaults.title) + })) + .addClass("OxMenuItem " + (self.options.disabled ? "OxDisabled" : "")) + .attr({ + id: Ox.toCamelCase(self.options.menu + "/" + self.options.id) + }) + .click(click) + .data("group", self.options.group) + .mouseenter(mouseenter) + .mouseleave(mouseleave); - that.addClass("OxItem " + (options.disabled ? "OxDisabled" : "")) - .attr({ - id: Ox.toCamelCase(options.menu + "/" + options.id) + // construct + that.append( + that.$status = $(" | ", { + addClass: "OxCell OxStatus", + html: self.options.checked ? oxui.symbols.check : "" + }) + ) + .append( + that.$icon = $(" | ", { + "addClass": "OxCell OxIcon" + }) + .append(self.options.icon ? + $("", { + attr: { + src: self.options.icon + } + }) : null + ) + ) + .append( + that.$title = $(" | ", { + addClass: "OxCell OxTitle", + html: self.options.title[0] + }) + ) + .append( + $(" | ", { + addClass: "OxCell OxModifiers", + html: $.map(self.options.keyboard.modifiers, function(modifier) { + return oxui.symbol[modifier]; + }).join("") + }) + ) + .append( + $(" | ", { + addClass: "OxCell Ox" + (self.options.submenu ? "Submenu" : "Key"), + html: self.options.submenu ? oxui.symbols.triangle_right : + oxui.symbols[self.options.keyboard.key] || self.options.keyboard.key + }) + ) + + function click() { + if (!that.hasClass("OxDisabled") && !self.options.submenu) { + menu.hideMenu(); + self.options.click(); + if (self.options.group && !self.options.checked) { + that.options({ + checked: true + }) + } + if (self.options.title.length == 2) { + that.toggleTitle(); + } + $eventHandler.trigger("clickMenu", self.options.id); + return false; + } + } + + function mouseenter() { + if (!that.is(".OxDisabled") && !that.is(".OxSelected")) { + $.each(self.options.menu.submenus, function(id, submenu) { + submenu.hideMenu(); + }); + $(".OxMenu .OxMenuItem[id!=" + self.options.id + "]").removeClass("selected"); + self.options.submenu && self.options.submenu.showMenu(); // fixme: do we want to switch to this style? + that.addClass("OxSelected"); + } + } + + function mouseleave() { + if (!that.hasClass("OxDisabled") && !self.options.submenu) { + that.removeClass("OxSelected"); + } + } + + function parseKeyboard(str) { + var modifiers = str.split(' '), + key = modifiers.pop(); + return { + modifiers: modifiers, + key: key + } + } + + self.onChange = function(key, value) { + if (key == "checked") { + that.$status.html(oxui.symbols.checked); + } else if (key == "disabled") { + that.toggleClass("disabled"); // fixme: this will only work if onChange is only invoked on actual change + } + } + + that.insertItemAfter = function(item) { + + }; + + that.insertItemBefore = function(item) { + + }; + + that.removeItem = function() { + + }; + + that.toggleChecked = function() { + + }; + + that.toggleDisabled = function() { + + }; + + that.toggleTitle = function() { + that.options({ + title: that.$title.html() == self.options.title[0] ? + self.options.title[1] : self.options.title[0] }) - .data("group", options.group) - .mouseenter() - .mouseleave() - .click(); + }; return that; - } + }; /* ============================================================================ |