From 7506f2618a33e938147d1b5c5a5053f3cfc145e7 Mon Sep 17 00:00:00 2001 From: meowcer Date: Sun, 15 Mar 2026 00:40:46 -0300 Subject: [PATCH 1/3] Context menu - initial test --- frontend/static/css/yourworld.css | 11 + frontend/static/yw/javascript/context_menu.js | 281 ++++++++++++++++++ frontend/static/yw/javascript/owot.js | 136 ++++++++- frontend/templates/yourworld.html | 1 + 4 files changed, 428 insertions(+), 1 deletion(-) create mode 100644 frontend/static/yw/javascript/context_menu.js diff --git a/frontend/static/css/yourworld.css b/frontend/static/css/yourworld.css index bea41f1..ec6d01c 100644 --- a/frontend/static/css/yourworld.css +++ b/frontend/static/css/yourworld.css @@ -629,3 +629,14 @@ a:visited { .longpress-option:active { background-color: #CDCDFF; } +.custom_ctx { + border: solid 1px #CCCCCC; + border-radius: 4px; + background-color: #AAAAAA; + padding: 2px; + position: absolute; + z-index: 200; +} +.custom_ctx_button { + padding: 0px 2px; +} \ No newline at end of file diff --git a/frontend/static/yw/javascript/context_menu.js b/frontend/static/yw/javascript/context_menu.js new file mode 100644 index 0000000..5f0ab54 --- /dev/null +++ b/frontend/static/yw/javascript/context_menu.js @@ -0,0 +1,281 @@ +function getContextMenuTop(menu) { + var top = menu; + while (top.parent) { + top = top.parent; + } + return top; +} +class ContextMenu { + constructor() { + var _this = this; + this.frame = document.createElement("div"); + this.frame.className = "custom_ctx"; + this.frame.style.display = "none"; + this.frame.style.flexDirection = "column"; + document.body.appendChild(this.frame); + + document.addEventListener("click", function(e) { + if (e.target.className == "custom_ctx" || e.target.className == "custom_ctx_button") return; + _this.close(); + }); + this.frame.addEventListener("contextmenu", function(e) { + e.preventDefault(); + }); + document.addEventListener("keydown", function(e) { + if (!checkKeyPress(e, keyConfig.reset)) return; + _this.close(); + }); + + this.frame.onmouseenter = function() { + var frame = _this; + while (frame) { + clearTimeout(frame.timeout); + frame = frame.parent; + } + } + this.frame.onmouseleave = function() { + var top = getContextMenuTop(_this); + if (_this == top) return; + var frame = top.dropdown; + frame.timeout = setTimeout(function() { + frame.close(); + }, _this.timeoutMs); + if (_this == frame) return; + _this.timeout = setTimeout(function() { + _this.close(); + }, _this.timeoutMs); + } + + this.width = null; + this.height = null; + this.isOpen = false; + this.hoveringId = null; + this.entries = []; + this.divisors = []; + + this.openFn = null; + this.moveFn = null; + this.closeFn = null; + this.submitFn = null; + + this.parent = null; + this.dropdown = null; + this.timeout = null; + this.timeoutMs = 400; + + ContextMenu.list.push(this); + } + addOption(text, action, rightAction, close = true) { + var _this = this; + var elm = document.createElement("button"); + elm.className = "custom_ctx_button"; + elm.innerText = text; + elm.style.minWidth = `${this.width}px`; + elm.onclick = function(e) { + if (!action) return; + action(e); + if (close) { + var top = getContextMenuTop(_this); + top.close(); + } + if (_this.submitFn) _this.submitFn(e); + } + elm.oncontextmenu = function(e) { + if (!rightAction) return; + rightAction(e); + if (close) { + var top = getContextMenuTop(_this); + top.close(); + } + if (_this.submitFn) _this.submitFn(e); + } + elm.onmouseover = function(e) { + _this.hoveringId = id; + if (_this.dropdown) _this.closeDropdown(); + } + + this.frame.appendChild(elm); + var id = this.entries.push({ + elm, + action, + rightAction + }); + return id - 1; + } + rename(id, text) { + if (!this.entries[id]) return; + this.entries[id].elm.innerText = text; + } + addDivisor() { + var divisor = document.createElement("div"); + divisor.style.backgroundColor = "#EEEEEE"; + divisor.style.border = "0px"; + divisor.style.height = "1px"; + divisor.style.margin = "1px"; + this.frame.appendChild(divisor); + var id = this.divisors.push(divisor); + return id - 1; + } + insertDivisor(id) { + if (!this.entries[id]) return; + var divisorId = this.addDivisor(); + var divisor = this.divisors[divisorId]; + this.frame.insertBefore(divisor, this.entries[id].elm); + return divisorId; + } + hideDivisor(id) { + if (!this.divisors[id]) return; + this.divisors[id].style.display = "none"; + } + move(x, y, spacingX = 0, spacingY = 0) { + var bounds = this.frame.getBoundingClientRect(); + var curWidth = bounds.width; + var curHeight = bounds.height; + x = Math.min(Math.max(x, 0), innerWidth); + y = Math.min(Math.max(y, 0), innerHeight); + if (innerWidth - x < curWidth && innerWidth > curWidth) x -= curWidth + spacingX - 1; + if (innerHeight - y < curHeight && innerHeight > curHeight) y -= curHeight + spacingY - 1; + this.frame.style.left = `${x}px`; + this.frame.style.top = `${y}px`; + this.closeDropdown(); + } + resize(width, height) { + if (width) { + this.frame.style.minWidth = `${width}px`; + this.width = width; + } + if (height) { + this.frame.style.minHeight = `${height}px`; + this.height = height; + } + this.entries.forEach(entry => { + if (width) entry.elm.style.minWidth = `${width}px`; + }); + } + direction(string) { + this.frame.style.flexDirection = string; + } + open(x, y, spacingX = 0, spacingY = 0) { + if (this.isOpen) { + this.move(x, y, spacingX, spacingY); + if (this.moveFn) this.moveFn(x, y, spacingX, spacingY); + return; + } + var shownButtons = this.entries.filter(x => x.elm.style.display != "none").length; + if (!shownButtons) { + var shownDivisors = this.divisors.filter(x => x.style.display != "none").length; + if (!shownDivisors) return; + } + ContextMenu.latest = this; + this.frame.style.display = "flex"; + this.isOpen = true; + if (this.openFn) this.openFn(x, y, spacingX, spacingY); + this.move(x, y, spacingX, spacingY); + } + close() { + if (!this.isOpen) return; + ContextMenu.latest = null; + this.frame.style.display = "none"; + this.isOpen = false; + if (this.closeFn) this.closeFn(); + this.closeDropdown(); + } + openDropdown(id) { + if (!this.entries[id]) return; + var button = this.entries[id].elm; + var bounds = this.frame.getBoundingClientRect(); + var curX = bounds.x; + var curWidth = bounds.width; + var buttonBounds = button.getBoundingClientRect(); + var buttonY = buttonBounds.y; + var buttonHeight = Math.round(buttonBounds.height); + this.dropdown.open(curX + curWidth, buttonY - 3, curWidth + 1, -buttonHeight - 5); + // -3: gap from frame to button + // +1: border + // -5 (+1-6): border and 2 gaps + } + closeDropdown() { + if (!this.dropdown) return; + this.dropdown.close(); + clearTimeout(this.dropdown.timeout); + } + setHover(id, contextMenu) { + if (!this.entries[id]) return; + var _this = this; + var elm = this.entries[id].elm; + contextMenu.parent = this; + + elm.onmouseenter = function(e) { + _this.dropdown = contextMenu; + _this.hoveringId = id; + if (this.disabled) return; + clearTimeout(_this.timeout); + if (_this.dropdown) clearTimeout(_this.dropdown.timeout); + var refreshDropdown = !_this.dropdown || _this.dropdown != contextMenu || !_this.dropdown.isOpen; + if (_this.dropdown && refreshDropdown) _this.closeDropdown(); + if (refreshDropdown) _this.openDropdown(id); + } + elm.onmouseleave = function() { + _this.dropdown.timeout = setTimeout(function() { + _this.closeDropdown(); + }, _this.timeoutMs); + } + } + clearHover(id, contextMenu) { + if (!this.entries[id]) return; + var elm = this.entries[id].elm; + elm.onmouseenter = () => {}; + elm.onmouseleave = () => {}; + if (this.hoveringId == id) this.closeDropdown(); + contextMenu.parent = null; + } + show(id) { + if (!this.entries[id]) return; + this.entries[id].elm.style.display = ""; + } + hide(id) { + if (!this.entries[id]) return; + this.entries[id].elm.style.display = "none"; + if (this.hoveringId == id) this.closeDropdown(); + } + showBoolean(id, boolean) { + if (!this.entries[id]) return; + this.entries[id].elm.style.display = boolean ? "" : "none"; + if (this.hoveringId != id || boolean) return; + this.closeDropdown(); + } + enable(id) { + if (!this.entries[id]) return; + this.entries[id].elm.disabled = false; + if (this.hoveringId == id) this.openDropdown(id); + } + disable(id) { + if (!this.entries[id]) return; + this.entries[id].elm.disabled = true; + if (this.hoveringId == id) this.closeDropdown(); + } + enableBoolean(id, boolean) { + if (!this.entries[id]) return; + this.entries[id].elm.disabled = !boolean; + if (this.hoveringId != id) return; + if (boolean) { + this.openDropdown(id); + } else { + this.closeDropdown(); + } + } + onOpen(callback) { + this.openFn = callback; + } + onMove(callback) { + this.moveFn = callback; + } + onClose(callback) { + this.closeFn = callback; + } + onSubmit(callback) { + this.submitFn = callback; + } +} +ContextMenu.list = []; +ContextMenu.latest = null; \ No newline at end of file diff --git a/frontend/static/yw/javascript/owot.js b/frontend/static/yw/javascript/owot.js index ad97521..a4d67db 100644 --- a/frontend/static/yw/javascript/owot.js +++ b/frontend/static/yw/javascript/owot.js @@ -49,7 +49,7 @@ var textRenderCtx; var cellWidthPad, tileW, tileH, cellW, cellH, font, specialCharFont, tileC, tileR, tileArea; var tileWidth, tileHeight; // exact tile dimensions for determining rendering size of tiles var dTileW, dTileH; // locked tile sizes for background image generation -var menu, menuStyle; +var menu, menuStyle, ctxMenu; var cursorCoords = null; // [tileX, tileY, charX, charY]; Coordinates of text cursor. If mouse is deselected, the value is null. var cursorCoordsCurrent = [0, 0, 0, 0, -1]; // [tileX, tileY, charX, charY]; cursorCoords that don't reset to null. @@ -1812,6 +1812,7 @@ function manageCoordHash() { homeY = coord[1]; w.doGoToCoord(coord[1], coord[0]); } + updateContextMenu(); } catch(e) { console.log(e); } @@ -5387,6 +5388,135 @@ function updateMenuEntryVisiblity() { w.menu.setEntryVisibility(menuOptions.eraseArea, permEraseArea); } +function buildContextMenu() { + ctxMenu = new ContextMenu(); + w.ctxMenu = {}; + ctxMenu.resize(60); + + w.ctxMenu.Goto = ctxMenu.addOption("Go to", function() { + w.ui.coordGotoModal.open(); + }); + w.ctxMenu.Menu_Goto = new ContextMenu(); + w.ctxMenu.Menu_Goto.addOption("Spawn", function() { + w.doGoToCoord(0, 0); + }); + w.ctxMenu.Goto_Home = w.ctxMenu.Menu_Goto.addOption(`Home (${homeX}, ${homeY})`, function() { + w.doGoToCoord(homeY, homeX); + }); + ctxMenu.setHover(w.ctxMenu.Goto, w.ctxMenu.Menu_Goto); + + w.ctxMenu.Spawn = ctxMenu.addOption("Go to spawn", function() { + w.doGoToCoord(0, 0); + }); + ctxMenu.hide(w.ctxMenu.Spawn); + + w.ctxMenu.Color = ctxMenu.addOption("Color", function() { + w.ui.colorModal.open(); + }); + w.ctxMenu.Menu_Color = new ContextMenu(); + w.ctxMenu.Color_Default = w.ctxMenu.Menu_Color.addOption("Default", function() { + w.changeColor(resolveColorValue(styles.text)); + }); + ctxMenu.setHover(w.ctxMenu.Color, w.ctxMenu.Menu_Color); + + w.ctxMenu.Create = ctxMenu.addOption("Create"); + w.ctxMenu.Menu_Create = new ContextMenu(); + w.ctxMenu.Create_Coord = w.ctxMenu.Menu_Create.addOption("Coord link", function() { + w.ui.coordLinkModal.open(); + }); + w.ctxMenu.Create_URL = w.ctxMenu.Menu_Create.addOption("URL link", function() { + w.ui.urlModal.open(); + }); + w.ctxMenu.Create_Protection = w.ctxMenu.Menu_Create.addOption("Protection"); + ctxMenu.setHover(w.ctxMenu.Create, w.ctxMenu.Menu_Create); + w.ctxMenu.Menu_Create_Protection = new ContextMenu(); + w.ctxMenu.Create_Owner = w.ctxMenu.Menu_Create_Protection.addOption("Owner-only", function() { + w.doProtect("owner-only"); + }); + w.ctxMenu.Create_Member = w.ctxMenu.Menu_Create_Protection.addOption("Member-only", function() { + w.doProtect("member-only"); + }); + w.ctxMenu.Create_Public = w.ctxMenu.Menu_Create_Protection.addOption("Public", function() { + w.doProtect("public"); + }); + w.ctxMenu.Create_Default = w.ctxMenu.Menu_Create_Protection.addOption("Default", function() { + w.doUnprotect(); + }); + w.ctxMenu.Menu_Create.setHover(w.ctxMenu.Create_Protection, w.ctxMenu.Menu_Create_Protection); + + w.ctxMenu.Erase = ctxMenu.addOption("Erase", function() { + startEraseUI(); + }); + + w.ctxMenu.Divisor = ctxMenu.addDivisor(); + + w.ctxMenu.Zoom = ctxMenu.addOption("Zoom", function() { + changeZoom(100); + }, null, false); + w.ctxMenu.Menu_Zoom = new ContextMenu(); + w.ctxMenu.Menu_Zoom.direction("row"); + w.ctxMenu.Menu_Zoom.resize(20); + w.ctxMenu.Menu_Zoom.addOption("+", function() { + changeZoom((userZoom * 100) * 1.5); + }, null, false); + w.ctxMenu.Menu_Zoom.addOption("-", function() { + changeZoom((userZoom * 100) / 1.5); + }, null, false); + ctxMenu.setHover(w.ctxMenu.Zoom, w.ctxMenu.Menu_Zoom); + + w.ctxMenu.Theme = ctxMenu.addOption("Theme"); + w.ctxMenu.Menu_Theme = new ContextMenu(); + w.ctxMenu.Menu_Theme.addOption("Default", function() { + w.day(true); + }); + w.ctxMenu.Menu_Theme.addOption("Day", function() { + w.day(); + }); + w.ctxMenu.Menu_Theme.addOption("Night", function() { + w.night(); + }); + ctxMenu.setHover(w.ctxMenu.Theme, w.ctxMenu.Menu_Theme); + + document.addEventListener("contextmenu", function(e) { + if (e.target != elm.main_view && e.target != elm.owot && e.target != elm.link_div) return; + e.preventDefault(); + ctxMenu.open(e.pageX, e.pageY); + }); +} + +function updateContextMenu() { + var permGoToCoord = Permissions.can_go_to_coord(state.userModel, state.worldModel); + var permColorText = Permissions.can_color_text(state.userModel, state.worldModel); + var permColorCell = Permissions.can_color_cell(state.userModel, state.worldModel); + var permCoordLink = Permissions.can_coordlink(state.userModel, state.worldModel); + var permUrlLink = Permissions.can_urllink(state.userModel, state.worldModel); + var permMemberArea = Permissions.can_protect_tiles(state.userModel, state.worldModel); + var permOwnerArea = Permissions.can_admin(state.userModel, state.worldModel); + var permEraseArea = Permissions.can_erase(state.userModel, state.worldModel); + + var showHome = permGoToCoord && (homeX != 0 || homeY != 0); + ctxMenu.showBoolean(w.ctxMenu.Goto, showHome); + ctxMenu.showBoolean(w.ctxMenu.Spawn, !showHome); + if (showHome) { + w.ctxMenu.Menu_Goto.rename(w.ctxMenu.Goto_Home, `Home (${homeX}, ${homeY})`); + } + + ctxMenu.enableBoolean(w.ctxMenu.Color, permColorText || permColorCell); + w.ctxMenu.Menu_Color.enableBoolean(w.ctxMenu.Color_Default, permColorText); + + w.ctxMenu.Menu_Create.enableBoolean(w.ctxMenu.Create_Coord, permCoordLink); + w.ctxMenu.Menu_Create.enableBoolean(w.ctxMenu.Create_URL, permUrlLink); + w.ctxMenu.Menu_Create.enableBoolean(w.ctxMenu.Create_Protection, permMemberArea); + if (permMemberArea) { + w.ctxMenu.Menu_Create_Protection.enableBoolean(w.ctxMenu.Create_Owner, permOwnerArea); + w.ctxMenu.Menu_Create_Protection.enable(w.ctxMenu.Create_Member); + w.ctxMenu.Menu_Create_Protection.enable(w.ctxMenu.Create_Member); + } + ctxMenu.enableBoolean(w.ctxMenu.Create, permCoordLink || permUrlLink || permMemberArea); + + ctxMenu.enableBoolean(w.ctxMenu.Erase, permEraseArea); +} + function regionSelectionsActive() { for(var i = 0; i < regionSelections.length; i++) { if(regionSelections[i].isSelecting) return true; @@ -7797,6 +7927,7 @@ function reapplyProperties(props) { resetColorModalVisibility(); updateColorModalPalette(); updateMenuEntryVisiblity(); + updateContextMenu(); updateWorldName(); w.reloadRenderer(); @@ -8167,6 +8298,7 @@ var ws_functions = { } } updateMenuEntryVisiblity(); + updateContextMenu(); if(paletteUpdated) { updateColorModalPalette(); } @@ -8345,6 +8477,8 @@ function begin() { buildMenu(); updateMenuEntryVisiblity(); + buildContextMenu(); + updateContextMenu(); w.regionSelect.onselection(handleRegionSelection); w.protectSelect.onselection(protectSelectionStart); diff --git a/frontend/templates/yourworld.html b/frontend/templates/yourworld.html index 902ea9e..26f5d19 100644 --- a/frontend/templates/yourworld.html +++ b/frontend/templates/yourworld.html @@ -142,6 +142,7 @@

Loading...

+ From 021230bb28a457715985e71302bff7164e46c911 Mon Sep 17 00:00:00 2001 From: meowcer Date: Sun, 15 Mar 2026 11:50:56 -0300 Subject: [PATCH 2/3] Frontend context menu theming --- frontend/static/css/yourworld.css | 22 +++- frontend/static/yw/javascript/context_menu.js | 101 +++++++++--------- frontend/static/yw/javascript/owot.js | 84 ++++++++++++++- 3 files changed, 154 insertions(+), 53 deletions(-) diff --git a/frontend/static/css/yourworld.css b/frontend/static/css/yourworld.css index ec6d01c..57977b5 100644 --- a/frontend/static/css/yourworld.css +++ b/frontend/static/css/yourworld.css @@ -630,13 +630,31 @@ a:visited { background-color: #CDCDFF; } .custom_ctx { - border: solid 1px #CCCCCC; - border-radius: 4px; background-color: #AAAAAA; + border: solid 1px; + border-color: #888888; + border-radius: 4px; + gap: 1px; padding: 2px; position: absolute; z-index: 200; } .custom_ctx_button { + background-color: #E6E6E6; + border: solid 1px; + border-color: #808080; + border-radius: 4px; padding: 0px 2px; +} +.custom_ctx_button:hover { + background-color: #D7D7D7; +} +.custom_ctx_button:disabled { + background-color: #BEBEBE; + color: #333333; +} +.custom_ctx_divisor { + background-color: #E6E6E6; + height: 1px; + margin: 1px; } \ No newline at end of file diff --git a/frontend/static/yw/javascript/context_menu.js b/frontend/static/yw/javascript/context_menu.js index 5f0ab54..967948c 100644 --- a/frontend/static/yw/javascript/context_menu.js +++ b/frontend/static/yw/javascript/context_menu.js @@ -6,21 +6,25 @@ function getContextMenuTop(menu) { return top; } class ContextMenu { - constructor() { - var _this = this; - this.frame = document.createElement("div"); + constructor() { + var _this = this; + this.frame = document.createElement("div"); this.frame.className = "custom_ctx"; - this.frame.style.display = "none"; - this.frame.style.flexDirection = "column"; - document.body.appendChild(this.frame); + this.frame.style.display = "none"; + this.frame.style.flexDirection = "column"; + document.body.appendChild(this.frame); - document.addEventListener("click", function(e) { - if (e.target.className == "custom_ctx" || e.target.className == "custom_ctx_button") return; - _this.close(); - }); - this.frame.addEventListener("contextmenu", function(e) { - e.preventDefault(); - }); + document.addEventListener("click", function(e) { + if ( + e.target.className == "custom_ctx" || + e.target.className == "custom_ctx_button" || + e.target.className == "custom_ctx_divisor" + ) return; + _this.close(); + }); + this.frame.addEventListener("contextmenu", function(e) { + e.preventDefault(); + }); document.addEventListener("keydown", function(e) { if (!checkKeyPress(e, keyConfig.reset)) return; _this.close(); @@ -50,7 +54,7 @@ class ContextMenu { this.height = null; this.isOpen = false; this.hoveringId = null; - this.entries = []; + this.entries = []; this.divisors = []; this.openFn = null; @@ -64,14 +68,14 @@ class ContextMenu { this.timeoutMs = 400; ContextMenu.list.push(this); - } - addOption(text, action, rightAction, close = true) { + } + addOption(text, action, rightAction, close = true) { var _this = this; - var elm = document.createElement("button"); + var elm = document.createElement("button"); elm.className = "custom_ctx_button"; - elm.innerText = text; + elm.innerText = text; elm.style.minWidth = `${this.width}px`; - elm.onclick = function(e) { + elm.onclick = function(e) { if (!action) return; action(e); if (close) { @@ -80,7 +84,7 @@ class ContextMenu { } if (_this.submitFn) _this.submitFn(e); } - elm.oncontextmenu = function(e) { + elm.oncontextmenu = function(e) { if (!rightAction) return; rightAction(e); if (close) { @@ -94,24 +98,21 @@ class ContextMenu { if (_this.dropdown) _this.closeDropdown(); } - this.frame.appendChild(elm); - var id = this.entries.push({ - elm, - action, + this.frame.appendChild(elm); + var id = this.entries.push({ + elm, + action, rightAction - }); + }); return id - 1; - } + } rename(id, text) { if (!this.entries[id]) return; this.entries[id].elm.innerText = text; } addDivisor() { var divisor = document.createElement("div"); - divisor.style.backgroundColor = "#EEEEEE"; - divisor.style.border = "0px"; - divisor.style.height = "1px"; - divisor.style.margin = "1px"; + divisor.className = "custom_ctx_divisor"; this.frame.appendChild(divisor); var id = this.divisors.push(divisor); return id - 1; @@ -128,34 +129,34 @@ class ContextMenu { this.divisors[id].style.display = "none"; } move(x, y, spacingX = 0, spacingY = 0) { - var bounds = this.frame.getBoundingClientRect(); - var curWidth = bounds.width; - var curHeight = bounds.height; + var bounds = this.frame.getBoundingClientRect(); + var curWidth = bounds.width; + var curHeight = bounds.height; x = Math.min(Math.max(x, 0), innerWidth); y = Math.min(Math.max(y, 0), innerHeight); - if (innerWidth - x < curWidth && innerWidth > curWidth) x -= curWidth + spacingX - 1; - if (innerHeight - y < curHeight && innerHeight > curHeight) y -= curHeight + spacingY - 1; + if (innerWidth - x < curWidth && innerWidth > curWidth) x -= curWidth + spacingX - 1; + if (innerHeight - y < curHeight && innerHeight > curHeight) y -= curHeight + spacingY - 1; this.frame.style.left = `${x}px`; - this.frame.style.top = `${y}px`; + this.frame.style.top = `${y}px`; this.closeDropdown(); } - resize(width, height) { - if (width) { - this.frame.style.minWidth = `${width}px`; - this.width = width; + resize(width, height) { + if (width) { + this.frame.style.minWidth = `${width}px`; + this.width = width; } - if (height) { - this.frame.style.minHeight = `${height}px`; - this.height = height; + if (height) { + this.frame.style.minHeight = `${height}px`; + this.height = height; } this.entries.forEach(entry => { if (width) entry.elm.style.minWidth = `${width}px`; }); - } + } direction(string) { this.frame.style.flexDirection = string; } - open(x, y, spacingX = 0, spacingY = 0) { + open(x, y, spacingX = 0, spacingY = 0) { if (this.isOpen) { this.move(x, y, spacingX, spacingY); if (this.moveFn) this.moveFn(x, y, spacingX, spacingY); @@ -170,16 +171,16 @@ class ContextMenu { this.frame.style.display = "flex"; this.isOpen = true; if (this.openFn) this.openFn(x, y, spacingX, spacingY); - this.move(x, y, spacingX, spacingY); - } - close() { + this.move(x, y, spacingX, spacingY); + } + close() { if (!this.isOpen) return; ContextMenu.latest = null; - this.frame.style.display = "none"; + this.frame.style.display = "none"; this.isOpen = false; if (this.closeFn) this.closeFn(); this.closeDropdown(); - } + } openDropdown(id) { if (!this.entries[id]) return; var button = this.entries[id].elm; diff --git a/frontend/static/yw/javascript/owot.js b/frontend/static/yw/javascript/owot.js index a4d67db..11f13e7 100644 --- a/frontend/static/yw/javascript/owot.js +++ b/frontend/static/yw/javascript/owot.js @@ -49,7 +49,7 @@ var textRenderCtx; var cellWidthPad, tileW, tileH, cellW, cellH, font, specialCharFont, tileC, tileR, tileArea; var tileWidth, tileHeight; // exact tile dimensions for determining rendering size of tiles var dTileW, dTileH; // locked tile sizes for background image generation -var menu, menuStyle, ctxMenu; +var menu, menuStyle, ctxMenu, ctxMenuStyle; var cursorCoords = null; // [tileX, tileY, charX, charY]; Coordinates of text cursor. If mouse is deselected, the value is null. var cursorCoordsCurrent = [0, 0, 0, 0, -1]; // [tileX, tileY, charX, charY]; cursorCoords that don't reset to null. @@ -1785,6 +1785,88 @@ function menu_color(color) { "}"; } +function context_menu_color(color) { + if(color.toLowerCase() == "#aaaaaa") { + if(window.ctxMenuStyle) { + window.ctxMenuStyle.remove(); + window.ctxMenuStyle = null; + } + return; + } + // change context menu color + if(!window.ctxMenuStyle) { + ctxMenuStyle = document.createElement("style"); + document.head.appendChild(ctxMenuStyle); + } + var rgb = int_to_rgb(resolveColorValue(color)); + var value = Math.max(rgb[0], rgb[1], rgb[2]); + var buttonDelta = 60; // 0xaaaaaa to 0xe6e6e6 + var buttonHoverDelta = 45; // to 0xd7 + var buttonBorderDelta = 42; // to 0x80 + var buttonDisabledDelta = 20; // to 0xbe + var outlineDelta = 34; // to 0x88 + var tColor = "#CCCCCC"; + var dColor = "#888888"; + if(value > 128) { + buttonBorderDelta = -42; + outlineDelta = -34; + tColor = "#000000"; + dColor = "#444444"; + } + + var buttonRgb = [ + Math.min(255, rgb[0] + buttonDelta), + Math.min(255, rgb[1] + buttonDelta), + Math.min(255, rgb[2] + buttonDelta) + ]; + var buttonHoverRgb = [ + Math.min(255, rgb[0] + buttonHoverDelta), + Math.min(255, rgb[1] + buttonHoverDelta), + Math.min(255, rgb[2] + buttonHoverDelta) + ]; + var buttonBorderRgb = [ + Math.max(0, rgb[0] + buttonBorderDelta), + Math.max(0, rgb[1] + buttonBorderDelta), + Math.max(0, rgb[2] + buttonBorderDelta) + ]; + var buttonDisabledRgb = [ + Math.min(255, rgb[0] + buttonDisabledDelta), + Math.min(255, rgb[1] + buttonDisabledDelta), + Math.min(255, rgb[2] + buttonDisabledDelta) + ]; + var outlineRgb = [ + Math.max(0, rgb[0] + outlineDelta), + Math.max(0, rgb[1] + outlineDelta), + Math.max(0, rgb[2] + outlineDelta) + ]; + + var buttonColor = int_to_hexcode(rgb_to_int(buttonRgb[0], buttonRgb[1], buttonRgb[2])); + var buttonHoverColor = int_to_hexcode(rgb_to_int(buttonHoverRgb[0], buttonHoverRgb[1], buttonHoverRgb[2])); + var buttonBorderColor = int_to_hexcode(rgb_to_int(buttonBorderRgb[0], buttonBorderRgb[1], buttonBorderRgb[2])); + var buttonDisabledColor = int_to_hexcode(rgb_to_int(buttonDisabledRgb[0], buttonDisabledRgb[1], buttonDisabledRgb[2])); + var outlineColor = int_to_hexcode(rgb_to_int(outlineRgb[0], outlineRgb[1], outlineRgb[2])); + + ctxMenuStyle.innerHTML = ".custom_ctx {" + + "background-color: " + color + ";" + + "border-color: " + outlineColor + ";" + + "}\n" + + ".custom_ctx_button {" + + "background-color: " + buttonColor + ";" + + "border-color: " + buttonBorderColor + ";" + + "color: " + tColor + ";" + + "}\n" + + ".custom_ctx_button:hover {" + + "background-color: " + buttonHoverColor + ";" + + "}\n" + + ".custom_ctx_button:disabled {" + + "background-color: " + buttonDisabledColor + ";" + + "color: " + dColor + ";" + + "}\n" + + ".custom_ctx_divisor {" + + "background-color: " + buttonColor + ";" + // repurposing + "}"; +} + function defaultStyles() { return { owner: "#ddd", From dad3a0ae8a41208f6b4c8bc38431dd2d396f090e Mon Sep 17 00:00:00 2001 From: meowcer Date: Wed, 18 Mar 2026 13:07:32 -0300 Subject: [PATCH 3/3] Use better formatting --- frontend/static/yw/javascript/owot.js | 52 ++++++++++++++------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/frontend/static/yw/javascript/owot.js b/frontend/static/yw/javascript/owot.js index 11f13e7..1c013f8 100644 --- a/frontend/static/yw/javascript/owot.js +++ b/frontend/static/yw/javascript/owot.js @@ -1840,31 +1840,33 @@ function context_menu_color(color) { Math.max(0, rgb[2] + outlineDelta) ]; - var buttonColor = int_to_hexcode(rgb_to_int(buttonRgb[0], buttonRgb[1], buttonRgb[2])); - var buttonHoverColor = int_to_hexcode(rgb_to_int(buttonHoverRgb[0], buttonHoverRgb[1], buttonHoverRgb[2])); - var buttonBorderColor = int_to_hexcode(rgb_to_int(buttonBorderRgb[0], buttonBorderRgb[1], buttonBorderRgb[2])); - var buttonDisabledColor = int_to_hexcode(rgb_to_int(buttonDisabledRgb[0], buttonDisabledRgb[1], buttonDisabledRgb[2])); - var outlineColor = int_to_hexcode(rgb_to_int(outlineRgb[0], outlineRgb[1], outlineRgb[2])); - - ctxMenuStyle.innerHTML = ".custom_ctx {" + - "background-color: " + color + ";" + - "border-color: " + outlineColor + ";" + - "}\n" + - ".custom_ctx_button {" + - "background-color: " + buttonColor + ";" + - "border-color: " + buttonBorderColor + ";" + - "color: " + tColor + ";" + - "}\n" + - ".custom_ctx_button:hover {" + - "background-color: " + buttonHoverColor + ";" + - "}\n" + - ".custom_ctx_button:disabled {" + - "background-color: " + buttonDisabledColor + ";" + - "color: " + dColor + ";" + - "}\n" + - ".custom_ctx_divisor {" + - "background-color: " + buttonColor + ";" + // repurposing - "}"; + var buttonColor = int_to_hexcode(rgb_to_int(...buttonRgb)); // applied to divisors as well + var buttonHoverColor = int_to_hexcode(rgb_to_int(...buttonHoverRgb)); + var buttonBorderColor = int_to_hexcode(rgb_to_int(...buttonBorderRgb)); + var buttonDisabledColor = int_to_hexcode(rgb_to_int(...buttonDisabledRgb)); + var outlineColor = int_to_hexcode(rgb_to_int(...outlineRgb)); + + ctxMenuStyle.innerHTML = ` + .custom_ctx { + background-color: ${color}; + border-color: ${outlineColor}; + } + .custom_ctx_button { + background-color: ${buttonColor}; + border-color: ${buttonBorderColor}; + color: ${tColor}; + } + .custom_ctx_button:hover { + background-color: ${buttonHoverColor}; + } + .custom_ctx_button:disabled { + background-color: ${buttonDisabledColor}; + color: ${dColor}; + } + .custom_ctx_divisor { + background-color: ${buttonColor}; + } + `; } function defaultStyles() {